diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 000000000..4dd7b0a31 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1 @@ +build: off diff --git a/.gitignore b/.gitignore index b165abf4b..52838918c 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ Profile.prof __pycache__/ *.py[cod] .env/ +.env* .~env/ .venv venv/ diff --git a/README.rst b/README.rst index 24b0c232a..e5e2dcc77 100644 --- a/README.rst +++ b/README.rst @@ -4,12 +4,10 @@ spaCy: Industrial-strength NLP spaCy is a library for advanced natural language processing in Python and Cython. spaCy is built on the very latest research, but it isn't researchware. It was designed from day one to be used in real products. spaCy currently supports -English, German and French, as well as tokenization for Spanish, Italian, -Portuguese, Dutch, Swedish, Finnish, Norwegian, Hungarian, Bengali, Hebrew, -Chinese and Japanese. It's commercial open-source software, released under the -MIT license. - -📊 **Help us improve the library!** `Take the spaCy user survey `_. +English, German, French and Spanish, as well as tokenization for Italian, +Portuguese, Dutch, Swedish, Finnish, Norwegian, Danish, Hungarian, Polish, +Bengali, Hebrew, Chinese and Japanese. It's commercial open-source software, +released under the MIT license. 💫 **Version 1.8 out now!** `Read the release notes here. `_ @@ -85,7 +83,7 @@ Features * GIL-free **multi-threading** * Efficient binary serialization * Easy **deep learning** integration -* Statistical models for **English** and **German** +* Statistical models for **English**, **German**, **French** and **Spanish** * State-of-the-art speed * Robust, rigorously evaluated accuracy @@ -197,7 +195,7 @@ To load a model, use ``spacy.load()`` with the model's shortcut link: .. code:: python import spacy - nlp = spacy.load('en_default') + nlp = spacy.load('en') doc = nlp(u'This is a sentence.') If you've installed a model via pip, you can also ``import`` it directly and @@ -313,7 +311,7 @@ and ``--model`` are optional and enable additional tests: # make sure you are using recent pytest version python -m pip install -U pytest - python -m pytest --vectors --models --slow + python -m pytest 🛠 Changelog ============ diff --git a/examples/training/train_ner.py b/examples/training/train_ner.py index bcc087d07..e9ae013d3 100644 --- a/examples/training/train_ner.py +++ b/examples/training/train_ner.py @@ -1,68 +1,27 @@ from __future__ import unicode_literals, print_function -import json -import pathlib + import random -import spacy -from spacy.pipeline import EntityRecognizer -from spacy.gold import GoldParse -from spacy.tagger import Tagger - - -try: - unicode -except: - unicode = str +from spacy.lang.en import English +from spacy.gold import GoldParse, biluo_tags_from_offsets -def train_ner(nlp, train_data, entity_types): - # Add new words to vocab. - for raw_text, _ in train_data: - doc = nlp.make_doc(raw_text) - for word in doc: - _ = nlp.vocab[word.orth] - - # Train NER. - ner = EntityRecognizer(nlp.vocab, entity_types=entity_types) - for itn in range(5): - random.shuffle(train_data) - for raw_text, entity_offsets in train_data: - doc = nlp.make_doc(raw_text) - gold = GoldParse(doc, entities=entity_offsets) - ner.update(doc, gold) - return ner - -def save_model(ner, model_dir): - model_dir = pathlib.Path(model_dir) - if not model_dir.exists(): - model_dir.mkdir() - assert model_dir.is_dir() - - with (model_dir / 'config.json').open('wb') as file_: - data = json.dumps(ner.cfg) - if isinstance(data, unicode): - data = data.encode('utf8') - file_.write(data) - ner.model.dump(str(model_dir / 'model')) - if not (model_dir / 'vocab').exists(): - (model_dir / 'vocab').mkdir() - ner.vocab.dump(str(model_dir / 'vocab' / 'lexemes.bin')) - with (model_dir / 'vocab' / 'strings.json').open('w', encoding='utf8') as file_: - ner.vocab.strings.dump(file_) +def reformat_train_data(tokenizer, examples): + """Reformat data to match JSON format""" + output = [] + for i, (text, entity_offsets) in enumerate(examples): + doc = tokenizer(text) + ner_tags = biluo_tags_from_offsets(tokenizer(text), entity_offsets) + words = [w.text for w in doc] + tags = ['-'] * len(doc) + heads = [0] * len(doc) + deps = [''] * len(doc) + sentence = (range(len(doc)), words, tags, heads, deps, ner_tags) + output.append((text, [(sentence, [])])) + return output def main(model_dir=None): - nlp = spacy.load('en', parser=False, entity=False, add_vectors=False) - - # v1.1.2 onwards - if nlp.tagger is None: - print('---- WARNING ----') - print('Data directory not found') - print('please run: `python -m spacy.en.download --force all` for better performance') - print('Using feature templates for tagging') - print('-----------------') - nlp.tagger = Tagger(nlp.vocab, features=Tagger.feature_templates) - train_data = [ ( 'Who is Shaka Khan?', @@ -74,23 +33,35 @@ def main(model_dir=None): (len('I like London and '), len('I like London and Berlin'), 'LOC')] ) ] - ner = train_ner(nlp, train_data, ['PERSON', 'LOC']) - - doc = nlp.make_doc('Who is Shaka Khan?') - nlp.tagger(doc) - ner(doc) - for word in doc: - print(word.text, word.orth, word.lower, word.tag_, word.ent_type_, word.ent_iob) - - if model_dir is not None: - save_model(ner, model_dir) - - - - + nlp = English(pipeline=['tensorizer', 'ner']) + get_data = lambda: reformat_train_data(nlp.tokenizer, train_data) + optimizer = nlp.begin_training(get_data) + for itn in range(100): + random.shuffle(train_data) + losses = {} + for raw_text, entity_offsets in train_data: + doc = nlp.make_doc(raw_text) + gold = GoldParse(doc, entities=entity_offsets) + nlp.update( + [doc], # Batch of Doc objects + [gold], # Batch of GoldParse objects + drop=0.5, # Dropout -- make it harder to memorise data + sgd=optimizer, # Callable to update weights + losses=losses) + print(losses) + print("Save to", model_dir) + nlp.to_disk(model_dir) + print("Load from", model_dir) + nlp = spacy.lang.en.English(pipeline=['tensorizer', 'ner']) + nlp.from_disk(model_dir) + for raw_text, _ in train_data: + doc = nlp(raw_text) + for word in doc: + print(word.text, word.ent_type_, word.ent_iob_) if __name__ == '__main__': - main('ner') + import plac + plac.call(main) # Who "" 2 # is "" 2 # Shaka "" PERSON 3 diff --git a/examples/training/train_textcat.py b/examples/training/train_textcat.py new file mode 100644 index 000000000..eefae111f --- /dev/null +++ b/examples/training/train_textcat.py @@ -0,0 +1,109 @@ +from __future__ import unicode_literals +import plac +import random +import tqdm + +from thinc.neural.optimizers import Adam +from thinc.neural.ops import NumpyOps +import thinc.extra.datasets + +import spacy.lang.en +from spacy.gold import GoldParse, minibatch +from spacy.util import compounding +from spacy.pipeline import TextCategorizer + + +def train_textcat(tokenizer, textcat, + train_texts, train_cats, dev_texts, dev_cats, + n_iter=20): + ''' + Train the TextCategorizer without associated pipeline. + ''' + textcat.begin_training() + optimizer = Adam(NumpyOps(), 0.001) + train_docs = [tokenizer(text) for text in train_texts] + train_gold = [GoldParse(doc, cats=cats) for doc, cats in + zip(train_docs, train_cats)] + train_data = zip(train_docs, train_gold) + batch_sizes = compounding(4., 128., 1.001) + for i in range(n_iter): + losses = {} + train_data = tqdm.tqdm(train_data, leave=False) # Progress bar + for batch in minibatch(train_data, size=batch_sizes): + docs, golds = zip(*batch) + textcat.update((docs, None), golds, sgd=optimizer, drop=0.2, + losses=losses) + with textcat.model.use_params(optimizer.averages): + scores = evaluate(tokenizer, textcat, dev_texts, dev_cats) + yield losses['textcat'], scores + + +def evaluate(tokenizer, textcat, texts, cats): + docs = (tokenizer(text) for text in texts) + tp = 1e-8 # True positives + fp = 1e-8 # False positives + fn = 1e-8 # False negatives + tn = 1e-8 # True negatives + for i, doc in enumerate(textcat.pipe(docs)): + gold = cats[i] + for label, score in doc.cats.items(): + if score >= 0.5 and label in gold: + tp += 1. + elif score >= 0.5 and label not in gold: + fp += 1. + elif score < 0.5 and label not in gold: + tn += 1 + if score < 0.5 and label in gold: + fn += 1 + precis = tp / (tp + fp) + recall = tp / (tp + fn) + fscore = 2 * (precis * recall) / (precis + recall) + return {'textcat_p': precis, 'textcat_r': recall, 'textcat_f': fscore} + + +def load_data(): + # Partition off part of the train data --- avoid running experiments + # against test. + train_data, _ = thinc.extra.datasets.imdb() + + random.shuffle(train_data) + + texts, labels = zip(*train_data) + cats = [(['POSITIVE'] if y else []) for y in labels] + + split = int(len(train_data) * 0.8) + + train_texts = texts[:split] + train_cats = cats[:split] + dev_texts = texts[split:] + dev_cats = cats[split:] + return (train_texts, train_cats), (dev_texts, dev_cats) + + +def main(model_loc=None): + nlp = spacy.lang.en.English() + tokenizer = nlp.tokenizer + textcat = TextCategorizer(tokenizer.vocab, labels=['POSITIVE']) + + print("Load IMDB data") + (train_texts, train_cats), (dev_texts, dev_cats) = load_data() + + print("Itn.\tLoss\tP\tR\tF") + progress = '{i:d} {loss:.3f} {textcat_p:.3f} {textcat_r:.3f} {textcat_f:.3f}' + + for i, (loss, scores) in enumerate(train_textcat(tokenizer, textcat, + train_texts, train_cats, + dev_texts, dev_cats, n_iter=20)): + print(progress.format(i=i, loss=loss, **scores)) + # How to save, load and use + nlp.pipeline.append(textcat) + if model_loc is not None: + nlp.to_disk(model_loc) + + nlp = spacy.load(model_loc) + doc = nlp(u'This movie sucked!') + print(doc.cats) + + +if __name__ == '__main__': + plac.call(main) diff --git a/requirements.txt b/requirements.txt index 62764725c..aae0f9388 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,8 +3,8 @@ pathlib numpy>=1.7 cymem>=1.30,<1.32 preshed>=1.0.0,<2.0.0 -thinc>=6.6.0,<6.7.0 -murmurhash>=0.26,<0.27 +thinc>=6.8.0,<6.9.0 +murmurhash>=0.28,<0.29 plac<1.0.0,>=0.9.6 six ujson>=1.35 @@ -14,3 +14,6 @@ regex==2017.4.5 ftfy>=4.4.2,<5.0.0 pytest>=3.0.6,<4.0.0 pip>=9.0.0,<10.0.0 +mock>=2.0.0,<3.0.0 +msgpack-python +msgpack-numpy diff --git a/setup.py b/setup.py index bedc1b42f..ecdf15536 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,8 @@ MOD_NAMES = [ 'spacy.matcher', 'spacy.syntax.ner', 'spacy.symbols', - 'spacy.syntax.iterators'] + 'spacy.vectors', +] COMPILE_OPTIONS = { @@ -188,10 +189,10 @@ def setup_package(): ext_modules=ext_modules, install_requires=[ 'numpy>=1.7', - 'murmurhash>=0.26,<0.27', + 'murmurhash>=0.28,<0.29', 'cymem>=1.30,<1.32', 'preshed>=1.0.0,<2.0.0', - 'thinc>=6.6.0,<6.7.0', + 'thinc>=6.8.0,<6.9.0', 'plac<1.0.0,>=0.9.6', 'pip>=9.0.0,<10.0.0', 'six', @@ -200,7 +201,9 @@ def setup_package(): 'dill>=0.2,<0.3', 'requests>=2.13.0,<3.0.0', 'regex==2017.4.5', - 'ftfy>=4.4.2,<5.0.0'], + 'ftfy>=4.4.2,<5.0.0', + 'msgpack-python', + 'msgpack-numpy'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', diff --git a/spacy/__init__.py b/spacy/__init__.py index 8dc0937f5..1cb7c0cbd 100644 --- a/spacy/__init__.py +++ b/spacy/__init__.py @@ -1,22 +1,22 @@ # coding: utf8 from __future__ import unicode_literals -import importlib - -from .compat import basestring_ -from .cli.info import info +from .cli.info import info as cli_info from .glossary import explain from .deprecated import resolve_load_name +from .about import __version__ from . import util def load(name, **overrides): name = resolve_load_name(name, **overrides) - model_path = util.resolve_model_path(name) - meta = util.parse_package_meta(model_path) - if 'lang' not in meta: - raise IOError('No language setting found in model meta.') - cls = util.get_lang_class(meta['lang']) - overrides['meta'] = meta - overrides['path'] = model_path - return cls(**overrides) + return util.load_model(name, **overrides) + + +def blank(name, **kwargs): + LangClass = util.get_lang_class(name) + return LangClass(**kwargs) + + +def info(model=None, markdown=False): + return cli_info(None, model, markdown) diff --git a/spacy/__main__.py b/spacy/__main__.py index acce3b7c8..214a7b617 100644 --- a/spacy/__main__.py +++ b/spacy/__main__.py @@ -3,135 +3,21 @@ from __future__ import print_function # NB! This breaks in plac on Python 2!! #from __future__ import unicode_literals -import plac -from spacy.cli import download as cli_download -from spacy.cli import link as cli_link -from spacy.cli import info as cli_info -from spacy.cli import package as cli_package -from spacy.cli import train as cli_train -from spacy.cli import model as cli_model -from spacy.cli import convert as cli_convert - - -class CLI(object): - """ - Command-line interface for spaCy - """ - commands = ('download', 'link', 'info', 'package', 'train', 'model', 'convert') - - @plac.annotations( - model=("model to download (shortcut or model name)", "positional", None, str), - direct=("force direct download. Needs model name with version and won't " - "perform compatibility check", "flag", "d", bool) - ) - def download(self, model, direct=False): - """ - Download compatible model from default download path using pip. Model - can be shortcut, model name or, if --direct flag is set, full model name - with version. - """ - cli_download(model, direct) - - - @plac.annotations( - origin=("package name or local path to model", "positional", None, str), - link_name=("name of shortuct link to create", "positional", None, str), - force=("force overwriting of existing link", "flag", "f", bool) - ) - def link(self, origin, link_name, force=False): - """ - Create a symlink for models within the spacy/data directory. Accepts - either the name of a pip package, or the local path to the model data - directory. Linking models allows loading them via spacy.load(link_name). - """ - cli_link(origin, link_name, force) - - - @plac.annotations( - model=("optional: shortcut link of model", "positional", None, str), - markdown=("generate Markdown for GitHub issues", "flag", "md", str) - ) - def info(self, model=None, markdown=False): - """ - Print info about spaCy installation. If a model shortcut link is - speficied as an argument, print model information. Flag --markdown - prints details in Markdown for easy copy-pasting to GitHub issues. - """ - cli_info(model, markdown) - - - @plac.annotations( - input_dir=("directory with model data", "positional", None, str), - output_dir=("output parent directory", "positional", None, str), - meta=("path to meta.json", "option", "m", str), - force=("force overwriting of existing folder in output directory", "flag", "f", bool) - ) - def package(self, input_dir, output_dir, meta=None, force=False): - """ - Generate Python package for model data, including meta and required - installation files. A new directory will be created in the specified - output directory, and model data will be copied over. - """ - cli_package(input_dir, output_dir, meta, force) - - - @plac.annotations( - lang=("model language", "positional", None, str), - output_dir=("output directory to store model in", "positional", None, str), - train_data=("location of JSON-formatted training data", "positional", None, str), - dev_data=("location of JSON-formatted development data (optional)", "positional", None, str), - n_iter=("number of iterations", "option", "n", int), - nsents=("number of sentences", "option", None, int), - parser_L1=("L1 regularization penalty for parser", "option", "L", float), - use_gpu=("Use GPU", "flag", "g", bool), - no_tagger=("Don't train tagger", "flag", "T", bool), - no_parser=("Don't train parser", "flag", "P", bool), - no_ner=("Don't train NER", "flag", "N", bool) - ) - def train(self, lang, output_dir, train_data, dev_data=None, n_iter=15, - nsents=0, parser_L1=0.0, use_gpu=False, - no_tagger=False, no_parser=False, no_ner=False): - """ - Train a model. Expects data in spaCy's JSON format. - """ - nsents = nsents or None - cli_train(lang, output_dir, train_data, dev_data, n_iter, nsents, - use_gpu, not no_tagger, not no_parser, not no_ner, parser_L1) - - @plac.annotations( - lang=("model language", "positional", None, str), - model_dir=("output directory to store model in", "positional", None, str), - freqs_data=("tab-separated frequencies file", "positional", None, str), - clusters_data=("Brown clusters file", "positional", None, str), - vectors_data=("word vectors file", "positional", None, str) - ) - def model(self, lang, model_dir, freqs_data, clusters_data=None, vectors_data=None): - """ - Initialize a new model and its data directory. - """ - cli_model(lang, model_dir, freqs_data, clusters_data, vectors_data) - - @plac.annotations( - input_file=("input file", "positional", None, str), - output_dir=("output directory for converted file", "positional", None, str), - n_sents=("Number of sentences per doc", "option", "n", float), - morphology=("Enable appending morphology to tags", "flag", "m", bool) - ) - def convert(self, input_file, output_dir, n_sents=10, morphology=False): - """ - Convert files into JSON format for use with train command and other - experiment management functions. - """ - cli_convert(input_file, output_dir, n_sents, morphology) - - - def __missing__(self, name): - print("\n Command %r does not exist." - "\n Use the --help flag for a list of available commands.\n" % name) - if __name__ == '__main__': import plac import sys - sys.argv[0] = 'spacy' - plac.Interpreter.call(CLI) + from spacy.cli import download, link, info, package, train, convert + from spacy.util import prints + + commands = {'download': download, 'link': link, 'info': info, 'train': train, + 'convert': convert, 'package': package} + if len(sys.argv) == 1: + prints(', '.join(commands), title="Available commands", exits=1) + command = sys.argv.pop(1) + sys.argv[0] = 'spacy %s' % command + if command in commands: + plac.call(commands[command]) + else: + prints("Available: %s" % ', '.join(commands), + title="Unknown command: %s" % command, exits=1) diff --git a/spacy/_ml.py b/spacy/_ml.py index d1dc64376..f7ab9b259 100644 --- a/spacy/_ml.py +++ b/spacy/_ml.py @@ -1,20 +1,107 @@ +import ujson from thinc.api import add, layerize, chain, clone, concatenate, with_flatten from thinc.neural import Model, Maxout, Softmax, Affine from thinc.neural._classes.hash_embed import HashEmbed from thinc.neural.ops import NumpyOps, CupyOps +from thinc.neural.util import get_array_module +import random +import cytoolz from thinc.neural._classes.convolution import ExtractWindow from thinc.neural._classes.static_vectors import StaticVectors from thinc.neural._classes.batchnorm import BatchNorm +from thinc.neural._classes.layernorm import LayerNorm as LN from thinc.neural._classes.resnet import Residual +from thinc.neural import ReLu +from thinc.neural._classes.selu import SELU from thinc import describe from thinc.describe import Dimension, Synapses, Biases, Gradient from thinc.neural._classes.affine import _set_dimensions_if_needed +from thinc.api import FeatureExtracter, with_getitem +from thinc.neural.pooling import Pooling, max_pool, mean_pool, sum_pool +from thinc.neural._classes.attention import ParametricAttention +from thinc.linear.linear import LinearModel +from thinc.api import uniqued, wrap, flatten_add_lengths -from .attrs import ID, LOWER, PREFIX, SUFFIX, SHAPE, TAG, DEP +from .attrs import ID, ORTH, LOWER, NORM, PREFIX, SUFFIX, SHAPE, TAG, DEP from .tokens.doc import Doc import numpy +import io + + +@layerize +def _flatten_add_lengths(seqs, pad=0, drop=0.): + ops = Model.ops + lengths = ops.asarray([len(seq) for seq in seqs], dtype='i') + def finish_update(d_X, sgd=None): + return ops.unflatten(d_X, lengths, pad=pad) + X = ops.flatten(seqs, pad=pad) + return (X, lengths), finish_update + + +@layerize +def _logistic(X, drop=0.): + xp = get_array_module(X) + if not isinstance(X, xp.ndarray): + X = xp.asarray(X) + # Clip to range (-10, 10) + X = xp.minimum(X, 10., X) + X = xp.maximum(X, -10., X) + Y = 1. / (1. + xp.exp(-X)) + def logistic_bwd(dY, sgd=None): + dX = dY * (Y * (1-Y)) + return dX + return Y, logistic_bwd + + +@layerize +def add_tuples(X, drop=0.): + """Give inputs of sequence pairs, where each sequence is (vals, length), + sum the values, returning a single sequence. + + If input is: + ((vals1, length), (vals2, length) + Output is: + (vals1+vals2, length) + + vals are a single tensor for the whole batch. + """ + (vals1, length1), (vals2, length2) = X + assert length1 == length2 + + def add_tuples_bwd(dY, sgd=None): + return (dY, dY) + + return (vals1+vals2, length), add_tuples_bwd + + +def _zero_init(model): + def _zero_init_impl(self, X, y): + self.W.fill(0) + model.on_data_hooks.append(_zero_init_impl) + if model.W is not None: + model.W.fill(0.) + return model + + +@layerize +def _preprocess_doc(docs, drop=0.): + keys = [doc.to_array([LOWER]) for doc in docs] + keys = [a[:, 0] for a in keys] + ops = Model.ops + lengths = ops.asarray([arr.shape[0] for arr in keys]) + keys = ops.xp.concatenate(keys) + vals = ops.allocate(keys.shape[0]) + 1 + return (keys, vals, lengths), None + + +def _init_for_precomputed(W, ops): + if (W**2).sum() != 0.: + return + reshaped = W.reshape((W.shape[1], W.shape[0] * W.shape[2])) + ops.xavier_uniform_init(reshaped) + W[:] = reshaped.reshape(W.shape) @describe.on_data(_set_dimensions_if_needed) @@ -23,8 +110,8 @@ import numpy nF=Dimension("Number of features"), nO=Dimension("Output size"), W=Synapses("Weights matrix", - lambda obj: (obj.nO, obj.nF, obj.nI), - lambda W, ops: ops.xavier_uniform_init(W)), + lambda obj: (obj.nF, obj.nO, obj.nI), + lambda W, ops: _init_for_precomputed(W, ops)), b=Biases("Bias vector", lambda obj: (obj.nO,)), d_W=Gradient("W"), @@ -39,25 +126,25 @@ class PrecomputableAffine(Model): def begin_update(self, X, drop=0.): # X: (b, i) - # Xf: (b, f, i) + # Yf: (b, f, i) # dY: (b, o) # dYf: (b, f, o) - #Yf = numpy.einsum('bi,ofi->bfo', X, self.W) + #Yf = numpy.einsum('bi,foi->bfo', X, self.W) Yf = self.ops.xp.tensordot( - X, self.W, axes=[[1], [2]]).transpose((0, 2, 1)) + X, self.W, axes=[[1], [2]]) Yf += self.b def backward(dY_ids, sgd=None): + tensordot = self.ops.xp.tensordot dY, ids = dY_ids Xf = X[ids] + #dXf = numpy.einsum('bo,foi->bfi', dY, self.W) + dXf = tensordot(dY, self.W, axes=[[1], [1]]) #dW = numpy.einsum('bo,bfi->ofi', dY, Xf) - dW = self.ops.xp.tensordot(dY, Xf, axes=[[0], [0]]) - db = dY.sum(axis=0) - #dXf = numpy.einsum('bo,ofi->bfi', dY, self.W) - dXf = self.ops.xp.tensordot(dY, self.W, axes=[[1], [0]]) - - self.d_W += dW - self.d_b += db + dW = tensordot(dY, Xf, axes=[[0], [0]]) + # ofi -> foi + self.d_W += dW.transpose((1, 0, 2)) + self.d_b += dY.sum(axis=0) if sgd is not None: sgd(self._mem.weights, self._mem.gradient, key=self.id) @@ -80,10 +167,10 @@ class PrecomputableAffine(Model): d_b=Gradient("b") ) class PrecomputableMaxouts(Model): - def __init__(self, nO=None, nI=None, nF=None, pieces=3, **kwargs): + def __init__(self, nO=None, nI=None, nF=None, nP=3, **kwargs): Model.__init__(self, **kwargs) self.nO = nO - self.nP = pieces + self.nP = nP self.nI = nI self.nF = nF @@ -120,38 +207,105 @@ class PrecomputableMaxouts(Model): return dXf return Yfp, backward -def Tok2Vec(width, embed_size, preprocess=None): - cols = [ID, LOWER, PREFIX, SUFFIX, SHAPE] - with Model.define_operators({'>>': chain, '|': concatenate, '**': clone, '+': add}): - lower = get_col(cols.index(LOWER)) >> HashEmbed(width, embed_size) - prefix = get_col(cols.index(PREFIX)) >> HashEmbed(width, embed_size//2) - suffix = get_col(cols.index(SUFFIX)) >> HashEmbed(width, embed_size//2) - shape = get_col(cols.index(SHAPE)) >> HashEmbed(width, embed_size//2) +def Tok2Vec(width, embed_size, preprocess=None): + cols = [ID, NORM, PREFIX, SUFFIX, SHAPE, ORTH] + with Model.define_operators({'>>': chain, '|': concatenate, '**': clone, '+': add}): + norm = get_col(cols.index(NORM)) >> HashEmbed(width, embed_size, name='embed_lower') + prefix = get_col(cols.index(PREFIX)) >> HashEmbed(width, embed_size//2, name='embed_prefix') + suffix = get_col(cols.index(SUFFIX)) >> HashEmbed(width, embed_size//2, name='embed_suffix') + shape = get_col(cols.index(SHAPE)) >> HashEmbed(width, embed_size//2, name='embed_shape') + + embed = (norm | prefix | suffix | shape ) tok2vec = ( - flatten - >> (lower | prefix | suffix | shape ) - >> Maxout(width, width*4, pieces=3) - >> Residual(ExtractWindow(nW=1) >> Maxout(width, width*3)) - >> Residual(ExtractWindow(nW=1) >> Maxout(width, width*3)) - >> Residual(ExtractWindow(nW=1) >> Maxout(width, width*3)) - >> Residual(ExtractWindow(nW=1) >> Maxout(width, width*3)) + with_flatten( + asarray(Model.ops, dtype='uint64') + >> uniqued(embed, column=5) + >> LN(Maxout(width, width*4, pieces=3)) + >> Residual(ExtractWindow(nW=1) >> SELU(width, width*3)) + >> Residual(ExtractWindow(nW=1) >> SELU(width, width*3)) + >> Residual(ExtractWindow(nW=1) >> SELU(width, width*3)) + >> Residual(ExtractWindow(nW=1) >> SELU(width, width*3)), + pad=4) ) if preprocess not in (False, None): tok2vec = preprocess >> tok2vec # Work around thinc API limitations :(. TODO: Revise in Thinc 7 tok2vec.nO = width + tok2vec.embed = embed return tok2vec -def get_col(idx): +def asarray(ops, dtype): def forward(X, drop=0.): + return ops.asarray(X, dtype=dtype), None + return layerize(forward) + + +def foreach(layer): + def forward(Xs, drop=0.): + results = [] + backprops = [] + for X in Xs: + result, bp = layer.begin_update(X, drop=drop) + results.append(result) + backprops.append(bp) + def backward(d_results, sgd=None): + dXs = [] + for d_result, backprop in zip(d_results, backprops): + dXs.append(backprop(d_result, sgd)) + return dXs + return results, backward + model = layerize(forward) + model._layers.append(layer) + return model + + +def rebatch(size, layer): + ops = layer.ops + def forward(X, drop=0.): + if X.shape[0] < size: + return layer.begin_update(X) + parts = _divide_array(X, size) + results, bp_results = zip(*[layer.begin_update(p, drop=drop) + for p in parts]) + y = ops.flatten(results) + def backward(dy, sgd=None): + d_parts = [bp(y, sgd=sgd) for bp, y in + zip(bp_results, _divide_array(dy, size))] + try: + dX = ops.flatten(d_parts) + except TypeError: + dX = None + except ValueError: + dX = None + return dX + return y, backward + model = layerize(forward) + model._layers.append(layer) + return model + + +def _divide_array(X, size): + parts = [] + index = 0 + while index < len(X): + parts.append(X[index : index + size]) + index += size + return parts + + +def get_col(idx): + assert idx >= 0, idx + def forward(X, drop=0.): + assert idx >= 0, idx if isinstance(X, numpy.ndarray): ops = NumpyOps() else: ops = CupyOps() output = ops.xp.ascontiguousarray(X[:, idx], dtype=X.dtype) def backward(y, sgd=None): + assert idx >= 0, idx dX = ops.allocate(X.shape) dX[:, idx] += y return dX @@ -167,21 +321,17 @@ def zero_init(model): def doc2feats(cols=None): - cols = [ID, LOWER, PREFIX, SUFFIX, SHAPE] + cols = [ID, NORM, PREFIX, SUFFIX, SHAPE, ORTH] def forward(docs, drop=0.): feats = [] for doc in docs: - if 'cached_feats' not in doc.user_data: - doc.user_data['cached_feats'] = model.ops.asarray( - doc.to_array(cols), - dtype='uint64') - feats.append(doc.user_data['cached_feats']) - assert feats[-1].dtype == 'uint64' + feats.append(doc.to_array(cols)) return feats, None model = layerize(forward) model.cols = cols return model + def print_shape(prefix): def forward(X, drop=0.): return X, lambda dX, **kwargs: dX @@ -197,6 +347,29 @@ def get_token_vectors(tokens_attrs_vectors, drop=0.): return vectors, backward +def fine_tune(embedding, combine=None): + if combine is not None: + raise NotImplementedError( + "fine_tune currently only supports addition. Set combine=None") + def fine_tune_fwd(docs_tokvecs, drop=0.): + docs, tokvecs = docs_tokvecs + lengths = model.ops.asarray([len(doc) for doc in docs], dtype='i') + + vecs, bp_vecs = embedding.begin_update(docs, drop=drop) + + output = embedding.ops.unflatten( + embedding.ops.flatten(tokvecs) + + embedding.ops.flatten(vecs), + lengths) + + def fine_tune_bwd(d_output, sgd=None): + bp_vecs(d_output, sgd=sgd) + return d_output + return output, fine_tune_bwd + model = wrap(fine_tune_fwd, embedding) + return model + + @layerize def flatten(seqs, drop=0.): if isinstance(seqs[0], numpy.ndarray): @@ -210,3 +383,95 @@ def flatten(seqs, drop=0.): return ops.unflatten(d_X, lengths) X = ops.xp.vstack(seqs) return X, finish_update + + +@layerize +def logistic(X, drop=0.): + xp = get_array_module(X) + if not isinstance(X, xp.ndarray): + X = xp.asarray(X) + # Clip to range (-10, 10) + X = xp.minimum(X, 10., X) + X = xp.maximum(X, -10., X) + Y = 1. / (1. + xp.exp(-X)) + def logistic_bwd(dY, sgd=None): + dX = dY * (Y * (1-Y)) + return dX + return Y, logistic_bwd + + +def zero_init(model): + def _zero_init_impl(self, X, y): + self.W.fill(0) + model.on_data_hooks.append(_zero_init_impl) + return model + +@layerize +def preprocess_doc(docs, drop=0.): + keys = [doc.to_array([LOWER]) for doc in docs] + keys = [a[:, 0] for a in keys] + ops = Model.ops + lengths = ops.asarray([arr.shape[0] for arr in keys]) + keys = ops.xp.concatenate(keys) + vals = ops.allocate(keys.shape[0]) + 1 + return (keys, vals, lengths), None + +def getitem(i): + def getitem_fwd(X, drop=0.): + return X[i], None + return layerize(getitem_fwd) + +def build_tagger_model(nr_class, token_vector_width, **cfg): + with Model.define_operators({'>>': chain, '+': add}): + # Input: (doc, tensor) tuples + private_tok2vec = Tok2Vec(token_vector_width, 7500, preprocess=doc2feats()) + + model = ( + fine_tune(private_tok2vec) + >> with_flatten( + Maxout(token_vector_width, token_vector_width) + >> Softmax(nr_class, token_vector_width) + ) + ) + model.nI = None + return model + + +def build_text_classifier(nr_class, width=64, **cfg): + nr_vector = cfg.get('nr_vector', 200) + with Model.define_operators({'>>': chain, '+': add, '|': concatenate, '**': clone}): + embed_lower = HashEmbed(width, nr_vector, column=1) + embed_prefix = HashEmbed(width//2, nr_vector, column=2) + embed_suffix = HashEmbed(width//2, nr_vector, column=3) + embed_shape = HashEmbed(width//2, nr_vector, column=4) + + cnn_model = ( + FeatureExtracter([ORTH, LOWER, PREFIX, SUFFIX, SHAPE]) + >> _flatten_add_lengths + >> with_getitem(0, + uniqued( + (embed_lower | embed_prefix | embed_suffix | embed_shape) + >> Maxout(width, width+(width//2)*3)) + >> Residual(ExtractWindow(nW=1) >> ReLu(width, width*3)) + >> Residual(ExtractWindow(nW=1) >> ReLu(width, width*3)) + >> Residual(ExtractWindow(nW=1) >> ReLu(width, width*3)) + ) + >> ParametricAttention(width,) + >> Pooling(sum_pool) + >> ReLu(width, width) + >> zero_init(Affine(nr_class, width, drop_factor=0.0)) + ) + linear_model = ( + _preprocess_doc + >> LinearModel(nr_class, drop_factor=0.) + ) + + model = ( + (linear_model | cnn_model) + >> zero_init(Affine(nr_class, nr_class*2, drop_factor=0.0)) + >> logistic + ) + + model.lsuv = False + return model + diff --git a/spacy/about.py b/spacy/about.py index 34ac75ccd..bf44c31d5 100644 --- a/spacy/about.py +++ b/spacy/about.py @@ -2,16 +2,16 @@ # https://python-packaging-user-guide.readthedocs.org/en/latest/single_source_version/ # https://github.com/pypa/warehouse/blob/master/warehouse/__about__.py -__title__ = 'spacy' -__version__ = '1.8.2' +__title__ = 'spacy-nightly' +__version__ = '2.0.0a7' __summary__ = 'Industrial-strength Natural Language Processing (NLP) with Python and Cython' __uri__ = 'https://spacy.io' -__author__ = 'Matthew Honnibal' -__email__ = 'matt@explosion.ai' +__author__ = 'Explosion AI' +__email__ = 'contact@explosion.ai' __license__ = 'MIT' __docs_models__ = 'https://spacy.io/docs/usage/models' __download_url__ = 'https://github.com/explosion/spacy-models/releases/download' __compatibility__ = 'https://raw.githubusercontent.com/explosion/spacy-models/master/compatibility.json' __shortcuts__ = 'https://raw.githubusercontent.com/explosion/spacy-models/master/shortcuts.json' -__model_files__ = 'https://raw.githubusercontent.com/explosion/spacy-dev-resources/master/templates/model/' +__model_files__ = 'https://raw.githubusercontent.com/explosion/spacy-dev-resources/develop/templates/model/' diff --git a/spacy/attrs.pxd b/spacy/attrs.pxd index 073de3565..a8ee9cac0 100644 --- a/spacy/attrs.pxd +++ b/spacy/attrs.pxd @@ -83,6 +83,7 @@ cpdef enum attr_id_t: ENT_IOB ENT_TYPE HEAD + SENT_START SPACY PROB diff --git a/spacy/attrs.pyx b/spacy/attrs.pyx index 49a1e0438..ba95e1e72 100644 --- a/spacy/attrs.pyx +++ b/spacy/attrs.pyx @@ -85,6 +85,7 @@ IDS = { "ENT_IOB": ENT_IOB, "ENT_TYPE": ENT_TYPE, "HEAD": HEAD, + "SENT_START": SENT_START, "SPACY": SPACY, "PROB": PROB, "LANG": LANG, @@ -149,6 +150,9 @@ def intify_attrs(stringy_attrs, strings_map=None, _do_deprecated=False): else: int_key = IDS[name.upper()] if strings_map is not None and isinstance(value, basestring): - value = strings_map[value] + if hasattr(strings_map, 'add'): + value = strings_map.add(value) + else: + value = strings_map[value] inty_attrs[int_key] = value return inty_attrs diff --git a/spacy/cli/__init__.py b/spacy/cli/__init__.py index d529096ef..2b4f98a88 100644 --- a/spacy/cli/__init__.py +++ b/spacy/cli/__init__.py @@ -2,6 +2,5 @@ from .download import download from .info import info from .link import link from .package import package -from .train import train, train_config -from .model import model +from .train import train from .convert import convert diff --git a/spacy/cli/convert.py b/spacy/cli/convert.py index c9a0510a8..a0a76e5ec 100644 --- a/spacy/cli/convert.py +++ b/spacy/cli/convert.py @@ -1,31 +1,43 @@ # coding: utf8 from __future__ import unicode_literals +import plac from pathlib import Path -from .converters import conllu2json +from .converters import conllu2json, iob2json from ..util import prints - # Converters are matched by file extension. To add a converter, add a new entry # to this dict with the file extension mapped to the converter function imported # from /converters. CONVERTERS = { '.conllu': conllu2json, - '.conll': conllu2json + '.conll': conllu2json, + '.iob': iob2json } -def convert(input_file, output_dir, *args): +@plac.annotations( + input_file=("input file", "positional", None, str), + output_dir=("output directory for converted file", "positional", None, str), + n_sents=("Number of sentences per doc", "option", "n", float), + morphology=("Enable appending morphology to tags", "flag", "m", bool) +) +def convert(cmd, input_file, output_dir, n_sents, morphology): + """ + Convert files into JSON format for use with train command and other + experiment management functions. + """ input_path = Path(input_file) output_path = Path(output_dir) if not input_path.exists(): - prints(input_path, title="Input file not found", exits=True) + prints(input_path, title="Input file not found", exits=1) if not output_path.exists(): - prints(output_path, title="Output directory not found", exits=True) + prints(output_path, title="Output directory not found", exits=1) file_ext = input_path.suffix if not file_ext in CONVERTERS: prints("Can't find converter for %s" % input_path.parts[-1], - title="Unknown format", exits=True) - CONVERTERS[file_ext](input_path, output_path, *args) + title="Unknown format", exits=1) + CONVERTERS[file_ext](input_path, output_path, + n_sents=n_sents, use_morphology=morphology) diff --git a/spacy/cli/converters/__init__.py b/spacy/cli/converters/__init__.py index a26b4ca3f..9026d16c6 100644 --- a/spacy/cli/converters/__init__.py +++ b/spacy/cli/converters/__init__.py @@ -1 +1,2 @@ from .conllu2json import conllu2json +from .iob2json import iob2json diff --git a/spacy/cli/converters/conllu2json.py b/spacy/cli/converters/conllu2json.py index 618810584..4d3fb58e4 100644 --- a/spacy/cli/converters/conllu2json.py +++ b/spacy/cli/converters/conllu2json.py @@ -73,10 +73,10 @@ def generate_sentence(sent): tokens = [] for i, id in enumerate(id_): token = {} - token["orth"] = word[id] - token["tag"] = tag[id] - token["head"] = head[id] - i - token["dep"] = dep[id] + token["orth"] = word[i] + token["tag"] = tag[i] + token["head"] = head[i] - id + token["dep"] = dep[i] tokens.append(token) sentence["tokens"] = tokens return sentence diff --git a/spacy/cli/converters/iob2json.py b/spacy/cli/converters/iob2json.py new file mode 100644 index 000000000..4849345e9 --- /dev/null +++ b/spacy/cli/converters/iob2json.py @@ -0,0 +1,45 @@ +# coding: utf8 +from __future__ import unicode_literals + +from ...compat import json_dumps, path2str +from ...util import prints +from ...gold import iob_to_biluo + + +def iob2json(input_path, output_path, n_sents=10, *a, **k): + """ + Convert IOB files into JSON format for use with train cli. + """ + # TODO: This isn't complete yet -- need to map from IOB to + # BILUO + with input_path.open('r', encoding='utf8') as file_: + docs = read_iob(file_) + + output_filename = input_path.parts[-1].replace(".iob", ".json") + output_file = output_path / output_filename + with output_file.open('w', encoding='utf-8') as f: + f.write(json_dumps(docs)) + prints("Created %d documents" % len(docs), + title="Generated output file %s" % path2str(output_file)) + + +def read_iob(file_): + sentences = [] + for line in file_: + if not line.strip(): + continue + tokens = [t.split('|') for t in line.split()] + if len(tokens[0]) == 3: + words, pos, iob = zip(*tokens) + else: + words, iob = zip(*tokens) + pos = ['-'] * len(words) + biluo = iob_to_biluo(iob) + sentences.append([ + {'orth': w, 'tag': p, 'ner': ent} + for (w, p, ent) in zip(words, pos, biluo) + ]) + sentences = [{'tokens': sent} for sent in sentences] + paragraphs = [{'sentences': [sent]} for sent in sentences] + docs = [{'id': 0, 'paragraphs': [para]} for para in paragraphs] + return docs diff --git a/spacy/cli/download.py b/spacy/cli/download.py index b6375a908..b6e5549da 100644 --- a/spacy/cli/download.py +++ b/spacy/cli/download.py @@ -1,6 +1,7 @@ # coding: utf8 from __future__ import unicode_literals +import plac import requests import os import subprocess @@ -11,7 +12,17 @@ from ..util import prints from .. import about -def download(model, direct=False): +@plac.annotations( + model=("model to download (shortcut or model name)", "positional", None, str), + direct=("force direct download. Needs model name with version and won't " + "perform compatibility check", "flag", "d", bool) +) +def download(cmd, model, direct=False): + """ + Download compatible model from default download path using pip. Model + can be shortcut, model name or, if --direct flag is set, full model name + with version. + """ if direct: download_model('{m}/{m}.tar.gz'.format(m=model)) else: @@ -20,7 +31,17 @@ def download(model, direct=False): compatibility = get_compatibility() version = get_version(model_name, compatibility) download_model('{m}-{v}/{m}-{v}.tar.gz'.format(m=model_name, v=version)) - link(model_name, model, force=True) + try: + link(None, model_name, model, force=True) + except: + # Dirty, but since spacy.download and the auto-linking is mostly + # a convenience wrapper, it's best to show a success message and + # loading instructions, even if linking fails. + prints("Creating a shortcut link for 'en' didn't work (maybe you " + "don't have admin permissions?), but you can still load " + "the model via its full package name:", + "nlp = spacy.load('%s')" % model_name, + title="Download successful") def get_json(url, desc): @@ -28,7 +49,7 @@ def get_json(url, desc): if r.status_code != 200: prints("Couldn't fetch %s. Please find a model for your spaCy installation " "(v%s), and download it manually." % (desc, about.__version__), - about.__docs_models__, title="Server error (%d)" % r.status_code, exits=True) + about.__docs_models__, title="Server error (%d)" % r.status_code, exits=1) return r.json() @@ -38,7 +59,7 @@ def get_compatibility(): comp = comp_table['spacy'] if version not in comp: prints("No compatible models found for v%s of spaCy." % version, - title="Compatibility error", exits=True) + title="Compatibility error", exits=1) return comp[version] @@ -46,7 +67,7 @@ def get_version(model, comp): if model not in comp: version = about.__version__ prints("No compatible model found for '%s' (spaCy v%s)." % (model, version), - title="Compatibility error", exits=True) + title="Compatibility error", exits=1) return comp[model][0] diff --git a/spacy/cli/info.py b/spacy/cli/info.py index c6b1b7631..5d45b271c 100644 --- a/spacy/cli/info.py +++ b/spacy/cli/info.py @@ -1,6 +1,7 @@ # coding: utf8 from __future__ import unicode_literals +import plac import platform from pathlib import Path @@ -9,17 +10,30 @@ from .. import about from .. import util -def info(model=None, markdown=False): +@plac.annotations( + model=("optional: shortcut link of model", "positional", None, str), + markdown=("generate Markdown for GitHub issues", "flag", "md", str) +) +def info(cmd, model=None, markdown=False): + """Print info about spaCy installation. If a model shortcut link is + speficied as an argument, print model information. Flag --markdown + prints details in Markdown for easy copy-pasting to GitHub issues. + """ if model: - data_path = util.get_data_path() - data = util.parse_package_meta(data_path / model, require=True) - model_path = Path(__file__).parent / data_path / model - if model_path.resolve() != model_path: - data['link'] = path2str(model_path) - data['source'] = path2str(model_path.resolve()) + if util.is_package(model): + model_path = util.get_package_path(model) else: - data['source'] = path2str(model_path) - print_info(data, 'model %s' % model, markdown) + model_path = util.get_data_path() / model + meta_path = model_path / 'meta.json' + if not meta_path.is_file(): + util.prints(meta_path, title="Can't find model meta.json", exits=1) + meta = util.read_json(meta_path) + if model_path.resolve() != model_path: + meta['link'] = path2str(model_path) + meta['source'] = path2str(model_path.resolve()) + else: + meta['source'] = path2str(model_path) + print_info(meta, 'model %s' % model, markdown) else: data = {'spaCy version': about.__version__, 'Location': path2str(Path(__file__).parent.parent), diff --git a/spacy/cli/link.py b/spacy/cli/link.py index 20d0473a3..a8ee01565 100644 --- a/spacy/cli/link.py +++ b/spacy/cli/link.py @@ -1,24 +1,36 @@ # coding: utf8 from __future__ import unicode_literals +import plac from pathlib import Path + from ..compat import symlink_to, path2str from ..util import prints from .. import util -def link(origin, link_name, force=False): +@plac.annotations( + origin=("package name or local path to model", "positional", None, str), + link_name=("name of shortuct link to create", "positional", None, str), + force=("force overwriting of existing link", "flag", "f", bool) +) +def link(cmd, origin, link_name, force=False): + """ + Create a symlink for models within the spacy/data directory. Accepts + either the name of a pip package, or the local path to the model data + directory. Linking models allows loading them via spacy.load(link_name). + """ if util.is_package(origin): - model_path = util.get_model_package_path(origin) + model_path = util.get_package_path(origin) else: model_path = Path(origin) if not model_path.exists(): prints("The data should be located in %s" % path2str(model_path), - title="Can't locate model data", exits=True) + title="Can't locate model data", exits=1) link_path = util.get_data_path() / link_name if link_path.exists() and not force: prints("To overwrite an existing link, use the --force flag.", - title="Link %s already exists" % link_name, exits=True) + title="Link %s already exists" % link_name, exits=1) elif link_path.exists(): link_path.unlink() try: @@ -33,5 +45,5 @@ def link(origin, link_name, force=False): title="Error: Couldn't link model to '%s'" % link_name) raise prints("%s --> %s" % (path2str(model_path), path2str(link_path)), - "You can now load the model via spacy.load('%s')." % link_name, + "You can now load the model via spacy.load('%s')" % link_name, title="Linking successful") diff --git a/spacy/cli/model.py b/spacy/cli/model.py deleted file mode 100644 index c69499f50..000000000 --- a/spacy/cli/model.py +++ /dev/null @@ -1,122 +0,0 @@ -# coding: utf8 -from __future__ import unicode_literals - -import gzip -import math -from ast import literal_eval -from preshed.counter import PreshCounter - -from ..vocab import write_binary_vectors -from ..compat import fix_text, path2str -from ..util import prints -from .. import util - - -def model(lang, model_dir, freqs_data, clusters_data, vectors_data): - model_path = util.ensure_path(model_dir) - freqs_path = util.ensure_path(freqs_data) - clusters_path = util.ensure_path(clusters_data) - vectors_path = util.ensure_path(vectors_data) - if not freqs_path.is_file(): - prints(freqs_path, title="No frequencies file found", exits=True) - if clusters_path and not clusters_path.is_file(): - prints(clusters_path, title="No Brown clusters file found", exits=True) - if vectors_path and not vectors_path.is_file(): - prints(vectors_path, title="No word vectors file found", exits=True) - vocab = util.get_lang_class(lang).Defaults.create_vocab() - probs, oov_prob = read_probs(freqs_path) - clusters = read_clusters(clusters_path) if clusters_path else {} - populate_vocab(vocab, clusters, probs, oov_prob) - create_model(model_path, vectors_path, vocab, oov_prob) - - -def create_model(model_path, vectors_path, vocab, oov_prob): - vocab_path = model_path / 'vocab' - lexemes_path = vocab_path / 'lexemes.bin' - strings_path = vocab_path / 'strings.json' - oov_path = vocab_path / 'oov_prob' - - if not model_path.exists(): - model_path.mkdir() - if not vocab_path.exists(): - vocab_path.mkdir() - vocab.dump(path2str(lexemes_path)) - with strings_path.open('w') as f: - vocab.strings.dump(f) - with oov_path.open('w') as f: - f.write('%f' % oov_prob) - if vectors_path: - vectors_dest = vocab_path / 'vec.bin' - write_binary_vectors(path2str(vectors_path), path2str(vectors_dest)) - - -def read_probs(freqs_path, max_length=100, min_doc_freq=5, min_freq=200): - counts = PreshCounter() - total = 0 - freqs_file = check_unzip(freqs_path) - for i, line in enumerate(freqs_file): - freq, doc_freq, key = line.rstrip().split('\t', 2) - freq = int(freq) - counts.inc(i+1, freq) - total += freq - counts.smooth() - log_total = math.log(total) - freqs_file = check_unzip(freqs_path) - probs = {} - for line in freqs_file: - freq, doc_freq, key = line.rstrip().split('\t', 2) - doc_freq = int(doc_freq) - freq = int(freq) - if doc_freq >= min_doc_freq and freq >= min_freq and len(key) < max_length: - word = literal_eval(key) - smooth_count = counts.smoother(int(freq)) - probs[word] = math.log(smooth_count) - log_total - oov_prob = math.log(counts.smoother(0)) - log_total - return probs, oov_prob - - -def read_clusters(clusters_path): - clusters = {} - with clusters_path.open() as f: - for line in f: - try: - cluster, word, freq = line.split() - word = fix_text(word) - except ValueError: - continue - # If the clusterer has only seen the word a few times, its - # cluster is unreliable. - if int(freq) >= 3: - clusters[word] = cluster - else: - clusters[word] = '0' - # Expand clusters with re-casing - for word, cluster in list(clusters.items()): - if word.lower() not in clusters: - clusters[word.lower()] = cluster - if word.title() not in clusters: - clusters[word.title()] = cluster - if word.upper() not in clusters: - clusters[word.upper()] = cluster - return clusters - - -def populate_vocab(vocab, clusters, probs, oov_prob): - for word, prob in reversed(sorted(list(probs.items()), key=lambda item: item[1])): - lexeme = vocab[word] - lexeme.prob = prob - lexeme.is_oov = False - # Decode as a little-endian string, so that we can do & 15 to get - # the first 4 bits. See _parse_features.pyx - if word in clusters: - lexeme.cluster = int(clusters[word][::-1], 2) - else: - lexeme.cluster = 0 - - -def check_unzip(file_path): - file_path_str = path2str(file_path) - if file_path_str.endswith('gz'): - return gzip.open(file_path_str) - else: - return file_path.open() diff --git a/spacy/cli/package.py b/spacy/cli/package.py index e6366c44e..1c720c2b5 100644 --- a/spacy/cli/package.py +++ b/spacy/cli/package.py @@ -1,6 +1,7 @@ # coding: utf8 from __future__ import unicode_literals +import plac import shutil import requests from pathlib import Path @@ -11,27 +12,38 @@ from .. import util from .. import about -def package(input_dir, output_dir, meta_path, force): +@plac.annotations( + input_dir=("directory with model data", "positional", None, str), + output_dir=("output parent directory", "positional", None, str), + meta=("path to meta.json", "option", "m", str), + force=("force overwriting of existing folder in output directory", "flag", "f", bool) +) +def package(cmd, input_dir, output_dir, meta=None, force=False): + """ + Generate Python package for model data, including meta and required + installation files. A new directory will be created in the specified + output directory, and model data will be copied over. + """ input_path = util.ensure_path(input_dir) output_path = util.ensure_path(output_dir) - meta_path = util.ensure_path(meta_path) + meta_path = util.ensure_path(meta) if not input_path or not input_path.exists(): - prints(input_path, title="Model directory not found", exits=True) + prints(input_path, title="Model directory not found", exits=1) if not output_path or not output_path.exists(): - prints(output_path, title="Output directory not found", exits=True) + prints(output_path, title="Output directory not found", exits=1) if meta_path and not meta_path.exists(): - prints(meta_path, title="meta.json not found", exits=True) + prints(meta_path, title="meta.json not found", exits=1) template_setup = get_template('setup.py') template_manifest = get_template('MANIFEST.in') - template_init = get_template('en_model_name/__init__.py') + template_init = get_template('xx_model_name/__init__.py') meta_path = meta_path or input_path / 'meta.json' if meta_path.is_file(): prints(meta_path, title="Reading meta.json from file") meta = util.read_json(meta_path) else: meta = generate_meta() - validate_meta(meta, ['lang', 'name', 'version']) + meta = validate_meta(meta, ['lang', 'name', 'version']) model_name = meta['lang'] + '_' + meta['name'] model_name_v = model_name + '-' + meta['version'] @@ -55,7 +67,7 @@ def create_dirs(package_path, force): else: prints(package_path, "Please delete the directory and try again, or " "use the --force flag to overwrite existing directories.", - title="Package directory already exists", exits=True) + title="Package directory already exists", exits=1) Path.mkdir(package_path, parents=True) @@ -68,31 +80,45 @@ def generate_meta(): settings = [('lang', 'Model language', 'en'), ('name', 'Model name', 'model'), ('version', 'Model version', '0.0.0'), - ('spacy_version', 'Required spaCy version', '>=2.0.0,<3.0.0'), + ('spacy_version', 'Required spaCy version', '>=%s,<3.0.0' % about.__version__), ('description', 'Model description', False), ('author', 'Author', False), ('email', 'Author email', False), ('url', 'Author website', False), ('license', 'License', 'CC BY-NC 3.0')] - prints("Enter the package settings for your model.", title="Generating meta.json") meta = {} for setting, desc, default in settings: response = util.get_raw_input(desc, default) meta[setting] = default if response == '' and default else response + meta['pipeline'] = generate_pipeline() + if about.__title__ != 'spacy': + meta['parent_package'] = about.__title__ return meta +def generate_pipeline(): + prints("If set to 'True', the default pipeline is used. If set to 'False', " + "the pipeline will be disabled. Components should be specified as a " + "comma-separated list of component names, e.g. vectorizer, tagger, " + "parser, ner. For more information, see the docs on processing pipelines.", + title="Enter your model's pipeline components") + pipeline = util.get_raw_input("Pipeline components", True) + replace = {'True': True, 'False': False} + return replace[pipeline] if pipeline in replace else pipeline.split(', ') + + def validate_meta(meta, keys): for key in keys: if key not in meta or meta[key] == '': prints("This setting is required to build your package.", - title='No "%s" setting found in meta.json' % key, exits=True) + title='No "%s" setting found in meta.json' % key, exits=1) + return meta def get_template(filepath): r = requests.get(about.__model_files__ + filepath) if r.status_code != 200: prints("Couldn't fetch template files from GitHub.", - title="Server error (%d)" % r.status_code, exits=True) + title="Server error (%d)" % r.status_code, exits=1) return r.text diff --git a/spacy/cli/train.py b/spacy/cli/train.py index 7ddc8d1cd..9ed621c12 100644 --- a/spacy/cli/train.py +++ b/spacy/cli/train.py @@ -1,132 +1,153 @@ # coding: utf8 from __future__ import unicode_literals, division, print_function +import plac import json from collections import defaultdict import cytoolz from pathlib import Path import dill +import tqdm +from thinc.neural.optimizers import linear_decay +from timeit import default_timer as timer from ..tokens.doc import Doc from ..scorer import Scorer from ..gold import GoldParse, merge_sents -from ..gold import read_json_file as read_gold_json +from ..gold import GoldCorpus, minibatch from ..util import prints from .. import util from .. import displacy +from ..compat import json_dumps -def train(language, output_dir, train_data, dev_data, n_iter, n_sents, - use_gpu, tagger, parser, ner, parser_L1): +@plac.annotations( + lang=("model language", "positional", None, str), + output_dir=("output directory to store model in", "positional", None, str), + train_data=("location of JSON-formatted training data", "positional", None, str), + dev_data=("location of JSON-formatted development data (optional)", "positional", None, str), + n_iter=("number of iterations", "option", "n", int), + n_sents=("number of sentences", "option", "ns", int), + use_gpu=("Use GPU", "option", "g", int), + resume=("Whether to resume training", "flag", "R", bool), + no_tagger=("Don't train tagger", "flag", "T", bool), + no_parser=("Don't train parser", "flag", "P", bool), + no_entities=("Don't train NER", "flag", "N", bool) +) +def train(cmd, lang, output_dir, train_data, dev_data, n_iter=20, n_sents=0, + use_gpu=-1, resume=False, no_tagger=False, no_parser=False, no_entities=False): + """ + Train a model. Expects data in spaCy's JSON format. + """ + util.set_env_log(True) + n_sents = n_sents or None output_path = util.ensure_path(output_dir) train_path = util.ensure_path(train_data) dev_path = util.ensure_path(dev_data) if not output_path.exists(): - prints(output_path, title="Output directory not found", exits=True) + output_path.mkdir() if not train_path.exists(): - prints(train_path, title="Training data not found", exits=True) + prints(train_path, title="Training data not found", exits=1) if dev_path and not dev_path.exists(): - prints(dev_path, title="Development data not found", exits=True) + prints(dev_path, title="Development data not found", exits=1) - lang = util.get_lang_class(language) - parser_cfg = { - 'pseudoprojective': True, - 'L1': parser_L1, - 'n_iter': n_iter, - 'lang': language, - 'features': lang.Defaults.parser_features} - entity_cfg = { - 'n_iter': n_iter, - 'lang': language, - 'features': lang.Defaults.entity_features} - tagger_cfg = { - 'n_iter': n_iter, - 'lang': language, - 'features': lang.Defaults.tagger_features} - gold_train = list(read_gold_json(train_path, limit=n_sents)) - gold_dev = list(read_gold_json(dev_path, limit=n_sents)) if dev_path else None + lang_class = util.get_lang_class(lang) - train_model(lang, gold_train, gold_dev, output_path, n_iter, use_gpu=use_gpu) - if gold_dev: - scorer = evaluate(lang, gold_dev, output_path) - print_results(scorer) + pipeline = ['token_vectors', 'tags', 'dependencies', 'entities'] + if no_tagger and 'tags' in pipeline: pipeline.remove('tags') + if no_parser and 'dependencies' in pipeline: pipeline.remove('dependencies') + if no_entities and 'entities' in pipeline: pipeline.remove('entities') + + # Take dropout and batch size as generators of values -- dropout + # starts high and decays sharply, to force the optimizer to explore. + # Batch size starts at 1 and grows, so that we make updates quickly + # at the beginning of training. + dropout_rates = util.decaying(util.env_opt('dropout_from', 0.2), + util.env_opt('dropout_to', 0.2), + util.env_opt('dropout_decay', 0.0)) + batch_sizes = util.compounding(util.env_opt('batch_from', 1), + util.env_opt('batch_to', 64), + util.env_opt('batch_compound', 1.001)) + + if resume: + prints(output_path / 'model19.pickle', title="Resuming training") + nlp = dill.load((output_path / 'model19.pickle').open('rb')) + else: + nlp = lang_class(pipeline=pipeline) + corpus = GoldCorpus(train_path, dev_path, limit=n_sents) + n_train_words = corpus.count_train() + + optimizer = nlp.begin_training(lambda: corpus.train_tuples, device=use_gpu) + + print("Itn.\tLoss\tUAS\tNER P.\tNER R.\tNER F.\tTag %\tToken %") + try: + for i in range(n_iter): + if resume: + i += 20 + with tqdm.tqdm(total=n_train_words, leave=False) as pbar: + train_docs = corpus.train_docs(nlp, projectivize=True, + gold_preproc=False, max_length=0) + losses = {} + for batch in minibatch(train_docs, size=batch_sizes): + docs, golds = zip(*batch) + nlp.update(docs, golds, sgd=optimizer, + drop=next(dropout_rates), losses=losses, + update_tensors=True) + pbar.update(sum(len(doc) for doc in docs)) + + with nlp.use_params(optimizer.averages): + util.set_env_log(False) + epoch_model_path = output_path / ('model%d' % i) + nlp.to_disk(epoch_model_path) + with (output_path / ('model%d.pickle' % i)).open('wb') as file_: + dill.dump(nlp, file_, -1) + nlp_loaded = lang_class(pipeline=pipeline) + nlp_loaded = nlp_loaded.from_disk(epoch_model_path) + scorer = nlp_loaded.evaluate( + corpus.dev_docs( + nlp_loaded, + gold_preproc=False)) + acc_loc =(output_path / ('model%d' % i) / 'accuracy.json') + with acc_loc.open('w') as file_: + file_.write(json_dumps(scorer.scores)) + util.set_env_log(True) + print_progress(i, losses, scorer.scores) + finally: + print("Saving model...") + with (output_path / 'model-final.pickle').open('wb') as file_: + with nlp.use_params(optimizer.averages): + dill.dump(nlp, file_, -1) -def train_config(config): - config_path = util.ensure_path(config) - if not config_path.is_file(): - prints(config_path, title="Config file not found", exits=True) - config = json.load(config_path) - for setting in []: - if setting not in config.keys(): - prints("%s not found in config file." % setting, title="Missing setting") +def _render_parses(i, to_render): + to_render[0].user_data['title'] = "Batch %d" % i + with Path('/tmp/entities.html').open('w') as file_: + html = displacy.render(to_render[:5], style='ent', page=True) + file_.write(html) + with Path('/tmp/parses.html').open('w') as file_: + html = displacy.render(to_render[:5], style='dep', page=True) + file_.write(html) -def train_model(Language, train_data, dev_data, output_path, n_iter, **cfg): - print("Itn.\tDep. Loss\tUAS\tNER F.\tTag %\tToken %") - - nlp = Language(pipeline=['token_vectors', 'tags', 'dependencies']) - dropout = util.env_opt('dropout', 0.0) - # TODO: Get spaCy using Thinc's trainer and optimizer - with nlp.begin_training(train_data, **cfg) as (trainer, optimizer): - for itn, epoch in enumerate(trainer.epochs(n_iter, gold_preproc=True)): - losses = defaultdict(float) - to_render = [] - for i, (docs, golds) in enumerate(epoch): - state = nlp.update(docs, golds, drop=dropout, sgd=optimizer) - losses['dep_loss'] += state.get('parser_loss', 0.0) - losses['tag_loss'] += state.get('tag_loss', 0.0) - to_render.insert(0, nlp(docs[-1].text)) - to_render[0].user_data['title'] = "Batch %d" % i - with Path('/tmp/entities.html').open('w') as file_: - html = displacy.render(to_render[:5], style='ent', page=True) - file_.write(html) - with Path('/tmp/parses.html').open('w') as file_: - html = displacy.render(to_render[:5], style='dep', page=True) - file_.write(html) - if dev_data: - with nlp.use_params(optimizer.averages): - dev_scores = trainer.evaluate(dev_data).scores - else: - dev_scores = defaultdict(float) - print_progress(itn, losses, dev_scores) - with (output_path / 'model.bin').open('wb') as file_: - dill.dump(nlp, file_, -1) - #nlp.to_disk(output_path, tokenizer=False) - - -def evaluate(Language, gold_tuples, path): - with (path / 'model.bin').open('rb') as file_: - nlp = dill.load(file_) - # TODO: - # 1. This code is duplicate with spacy.train.Trainer.evaluate - # 2. There's currently a semantic difference between pipe and - # not pipe! It matters whether we batch the inputs. Must fix! - all_docs = [] - all_golds = [] - for raw_text, paragraph_tuples in dev_sents: - if gold_preproc: - raw_text = None - else: - paragraph_tuples = merge_sents(paragraph_tuples) - docs = self.make_docs(raw_text, paragraph_tuples) - golds = self.make_golds(docs, paragraph_tuples) - all_docs.extend(docs) - all_golds.extend(golds) - scorer = Scorer() - for doc, gold in zip(self.nlp.pipe(all_docs), all_golds): - scorer.score(doc, gold) - return scorer - - -def print_progress(itn, losses, dev_scores): - # TODO: Fix! +def print_progress(itn, losses, dev_scores, wps=0.0): scores = {} - for col in ['dep_loss', 'tag_loss', 'uas', 'tags_acc', 'token_acc', 'ents_f']: + for col in ['dep_loss', 'tag_loss', 'uas', 'tags_acc', 'token_acc', + 'ents_p', 'ents_r', 'ents_f', 'wps']: scores[col] = 0.0 - scores.update(losses) + scores['dep_loss'] = losses.get('parser', 0.0) + scores['tag_loss'] = losses.get('tagger', 0.0) scores.update(dev_scores) - tpl = '{:d}\t{dep_loss:.3f}\t{tag_loss:.3f}\t{uas:.3f}\t{ents_f:.3f}\t{tags_acc:.3f}\t{token_acc:.3f}' + scores['wps'] = wps + tpl = '\t'.join(( + '{:d}', + '{dep_loss:.3f}', + '{uas:.3f}', + '{ents_p:.3f}', + '{ents_r:.3f}', + '{ents_f:.3f}', + '{tags_acc:.3f}', + '{token_acc:.3f}', + '{wps:.1f}')) print(tpl.format(itn, **scores)) diff --git a/spacy/compat.py b/spacy/compat.py index 2a551a831..4ef24cd8b 100644 --- a/spacy/compat.py +++ b/spacy/compat.py @@ -5,6 +5,9 @@ import six import ftfy import sys import ujson +import itertools + +from thinc.neural.util import copy_array try: import cPickle as pickle @@ -32,6 +35,8 @@ copy_reg = copy_reg CudaStream = CudaStream cupy = cupy fix_text = ftfy.fix_text +copy_array = copy_array +izip = getattr(itertools, 'izip', zip) is_python2 = six.PY2 is_python3 = six.PY3 @@ -57,6 +62,19 @@ elif is_python3: path2str = lambda path: str(path) +def b_to_str(b_str): + if is_python2: + return b_str + # important: if no encoding is set, string becomes "b'...'" + return str(b_str, encoding='utf8') + + +def getattr_(obj, name, *default): + if is_python3 and isinstance(name, bytes): + name = name.decode('utf8') + return getattr(obj, name, *default) + + def symlink_to(orig, dest): if is_python2 and is_windows: import subprocess @@ -71,3 +89,16 @@ def is_config(python2=None, python3=None, windows=None, linux=None, osx=None): (windows == None or windows == is_windows) and (linux == None or linux == is_linux) and (osx == None or osx == is_osx)) + + +def normalize_string_keys(old): + '''Given a dictionary, make sure keys are unicode strings, not bytes.''' + new = {} + for key, value in old.items(): + if isinstance(key, bytes_): + new[key.decode('utf8')] = value + else: + new[key] = value + return new + + diff --git a/spacy/displacy/__init__.py b/spacy/displacy/__init__.py index f338a2e6c..7c479f94c 100644 --- a/spacy/displacy/__init__.py +++ b/spacy/displacy/__init__.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals from .render import DependencyRenderer, EntityRenderer from ..tokens import Doc +from ..compat import b_to_str from ..util import prints, is_in_jupyter @@ -10,27 +11,28 @@ _html = {} IS_JUPYTER = is_in_jupyter() -def render(docs, style='dep', page=False, minify=False, jupyter=IS_JUPYTER, options={}): +def render(docs, style='dep', page=False, minify=False, jupyter=IS_JUPYTER, + options={}, manual=False): """Render displaCy visualisation. docs (list or Doc): Document(s) to visualise. style (unicode): Visualisation style, 'dep' or 'ent'. page (bool): Render markup as full HTML page. minify (bool): Minify HTML markup. - jupyter (bool): Experimental, use Jupyter's display() to output markup. + jupyter (bool): Experimental, use Jupyter's `display()` to output markup. options (dict): Visualiser-specific options, e.g. colors. + manual (bool): Don't parse `Doc` and instead, expect a dict or list of dicts. RETURNS (unicode): Rendered HTML markup. """ - if isinstance(docs, Doc): - docs = [docs] - if style == 'dep': - renderer = DependencyRenderer(options=options) - parsed = [parse_deps(doc, options) for doc in docs] - elif style == 'ent': - renderer = EntityRenderer(options=options) - parsed = [parse_ents(doc, options) for doc in docs] - else: + factories = {'dep': (DependencyRenderer, parse_deps), + 'ent': (EntityRenderer, parse_ents)} + if style not in factories: raise ValueError("Unknown style: %s" % style) + if isinstance(docs, Doc) or isinstance(docs, dict): + docs = [docs] + renderer, converter = factories[style] + renderer = renderer(options=options) + parsed = [converter(doc, options) for doc in docs] if not manual else docs _html['parsed'] = renderer.render(parsed, page=page, minify=minify).strip() html = _html['parsed'] if jupyter: # return HTML rendered by IPython display() @@ -39,7 +41,8 @@ def render(docs, style='dep', page=False, minify=False, jupyter=IS_JUPYTER, opti return html -def serve(docs, style='dep', page=True, minify=False, options={}, port=5000): +def serve(docs, style='dep', page=True, minify=False, options={}, manual=False, + port=5000): """Serve displaCy visualisation. docs (list or Doc): Document(s) to visualise. @@ -47,27 +50,36 @@ def serve(docs, style='dep', page=True, minify=False, options={}, port=5000): page (bool): Render markup as full HTML page. minify (bool): Minify HTML markup. options (dict): Visualiser-specific options, e.g. colors. + manual (bool): Don't parse `Doc` and instead, expect a dict or list of dicts. port (int): Port to serve visualisation. """ from wsgiref import simple_server - render(docs, style=style, page=page, minify=minify, options=options) + render(docs, style=style, page=page, minify=minify, options=options, manual=manual) httpd = simple_server.make_server('0.0.0.0', port, app) prints("Using the '%s' visualizer" % style, title="Serving on port %d..." % port) - httpd.serve_forever() + try: + httpd.serve_forever() + except KeyboardInterrupt: + prints("Shutting down server on port %d." % port) + finally: + httpd.server_close() def app(environ, start_response): - start_response('200 OK', [('Content-type', 'text/html; charset=utf-8')]) + # headers and status need to be bytes in Python 2, see #1227 + headers = [(b_to_str(b'Content-type'), b_to_str(b'text/html; charset=utf-8'))] + start_response(b_to_str(b'200 OK'), headers) res = _html['parsed'].encode(encoding='utf-8') return [res] -def parse_deps(doc, options={}): +def parse_deps(orig_doc, options={}): """Generate dependency parse in {'words': [], 'arcs': []} format. doc (Doc): Document do parse. RETURNS (dict): Generated dependency parse keyed by words and arcs. """ + doc = Doc(orig_doc.vocab).from_bytes(orig_doc.to_bytes()) if options.get('collapse_punct', True): spans = [] for word in doc[:-1]: diff --git a/spacy/displacy/render.py b/spacy/displacy/render.py index 6a786437a..1050ffa87 100644 --- a/spacy/displacy/render.py +++ b/spacy/displacy/render.py @@ -18,12 +18,11 @@ class DependencyRenderer(object): offset_x, color, bg, font) """ self.compact = options.get('compact', False) - distance, arrow_width = (85, 8) if self.compact else (175, 10) self.word_spacing = options.get('word_spacing', 45) - self.arrow_spacing = options.get('arrow_spacing', 20) - self.arrow_width = options.get('arrow_width', arrow_width) + self.arrow_spacing = options.get('arrow_spacing', 12 if self.compact else 20) + self.arrow_width = options.get('arrow_width', 6 if self.compact else 10) self.arrow_stroke = options.get('arrow_stroke', 2) - self.distance = options.get('distance', distance) + self.distance = options.get('distance', 150 if self.compact else 175) self.offset_x = options.get('offset_x', 50) self.color = options.get('color', '#000000') self.bg = options.get('bg', '#ffffff') @@ -99,6 +98,8 @@ class DependencyRenderer(object): x_end = (self.offset_x+(end-start)*self.distance+start*self.distance -self.arrow_spacing*(self.highest_level-level)/4) y_curve = self.offset_y-level*self.distance/2 + if self.compact: + y_curve = self.offset_y-level*self.distance/6 if y_curve == 0 and len(self.levels) > 5: y_curve = -self.distance arrowhead = self.get_arrowhead(direction, x_start, y, x_end) @@ -175,7 +176,7 @@ class EntityRenderer(object): minify (bool): Minify HTML markup. RETURNS (unicode): Rendered HTML markup. """ - rendered = [self.render_ents(p['text'], p['ents'], p['title']) for p in parsed] + rendered = [self.render_ents(p['text'], p['ents'], p.get('title', None)) for p in parsed] if page: docs = ''.join([TPL_FIGURE.format(content=doc) for doc in rendered]) markup = TPL_PAGE.format(content=docs) diff --git a/spacy/displacy/templates.py b/spacy/displacy/templates.py index 54df44489..2f6fc22de 100644 --- a/spacy/displacy/templates.py +++ b/spacy/displacy/templates.py @@ -21,7 +21,7 @@ TPL_DEP_WORDS = """ TPL_DEP_ARCS = """ - + {label} diff --git a/spacy/gold.pxd b/spacy/gold.pxd index 0afdab46d..5873b23ac 100644 --- a/spacy/gold.pxd +++ b/spacy/gold.pxd @@ -1,13 +1,15 @@ from cymem.cymem cimport Pool from .structs cimport TokenC +from .typedefs cimport attr_t from .syntax.transition_system cimport Transition cdef struct GoldParseC: int* tags int* heads - int* labels + int* has_dep + attr_t* labels int** brackets Transition* ner @@ -18,15 +20,16 @@ cdef class GoldParse: cdef GoldParseC c cdef int length - cdef readonly int loss - cdef readonly list words - cdef readonly list tags - cdef readonly list heads - cdef readonly list labels - cdef readonly dict orths - cdef readonly list ner - cdef readonly list ents - cdef readonly dict brackets + cdef public int loss + cdef public list words + cdef public list tags + cdef public list heads + cdef public list labels + cdef public dict orths + cdef public list ner + cdef public list ents + cdef public dict brackets + cdef public object cats cdef readonly list cand_to_gold cdef readonly list gold_to_cand diff --git a/spacy/gold.pyx b/spacy/gold.pyx index 7e00030a4..39951447c 100644 --- a/spacy/gold.pyx +++ b/spacy/gold.pyx @@ -5,10 +5,13 @@ from __future__ import unicode_literals, print_function import io import re import ujson +import random +import cytoolz from .syntax import nonproj from .util import ensure_path from . import util +from .tokens import Doc def tags_to_entities(tags): @@ -86,8 +89,8 @@ def _min_edit_path(cand_words, gold_words): # TODO: Fix this --- just do it properly, make the full edit matrix and # then walk back over it... # Preprocess inputs - cand_words = [punct_re.sub('', w) for w in cand_words] - gold_words = [punct_re.sub('', w) for w in gold_words] + cand_words = [punct_re.sub('', w).lower() for w in cand_words] + gold_words = [punct_re.sub('', w).lower() for w in gold_words] if cand_words == gold_words: return 0, ''.join(['M' for _ in gold_words]) @@ -139,8 +142,164 @@ def _min_edit_path(cand_words, gold_words): return prev_costs[n_gold], previous_row[-1] -def read_json_file(loc, docs_filter=None, make_supertags=True, limit=None): - make_supertags = util.env_opt('make_supertags', make_supertags) +def minibatch(items, size=8): + '''Iterate over batches of items. `size` may be an iterator, + so that batch-size can vary on each step. + ''' + items = iter(items) + while True: + batch_size = next(size) #if hasattr(size, '__next__') else size + batch = list(cytoolz.take(int(batch_size), items)) + if len(batch) == 0: + break + yield list(batch) + + +class GoldCorpus(object): + """An annotated corpus, using the JSON file format. Manages + annotations for tagging, dependency parsing and NER.""" + def __init__(self, train_path, dev_path, gold_preproc=True, limit=None): + """Create a GoldCorpus. + + train_path (unicode or Path): File or directory of training data. + dev_path (unicode or Path): File or directory of development data. + """ + self.train_path = util.ensure_path(train_path) + self.dev_path = util.ensure_path(dev_path) + self.limit = limit + self.train_locs = self.walk_corpus(self.train_path) + self.dev_locs = self.walk_corpus(self.dev_path) + + @property + def train_tuples(self): + i = 0 + for loc in self.train_locs: + gold_tuples = read_json_file(loc) + for item in gold_tuples: + yield item + i += len(item[1]) + if self.limit and i >= self.limit: + break + + @property + def dev_tuples(self): + i = 0 + for loc in self.dev_locs: + gold_tuples = read_json_file(loc) + for item in gold_tuples: + yield item + i += 1 + if self.limit and i >= self.limit: + break + + def count_train(self): + n = 0 + i = 0 + for raw_text, paragraph_tuples in self.train_tuples: + n += sum([len(s[0][1]) for s in paragraph_tuples]) + if self.limit and i >= self.limit: + break + i += len(paragraph_tuples) + return n + + def train_docs(self, nlp, gold_preproc=False, + projectivize=False, max_length=None, + noise_level=0.0): + train_tuples = self.train_tuples + if projectivize: + train_tuples = nonproj.preprocess_training_data( + self.train_tuples) + random.shuffle(train_tuples) + gold_docs = self.iter_gold_docs(nlp, train_tuples, gold_preproc, + max_length=max_length, + noise_level=noise_level) + yield from gold_docs + + def dev_docs(self, nlp, gold_preproc=False): + gold_docs = self.iter_gold_docs(nlp, self.dev_tuples, gold_preproc) + #gold_docs = nlp.preprocess_gold(gold_docs) + yield from gold_docs + + @classmethod + def iter_gold_docs(cls, nlp, tuples, gold_preproc, max_length=None, + noise_level=0.0): + for raw_text, paragraph_tuples in tuples: + if gold_preproc: + raw_text = None + else: + paragraph_tuples = merge_sents(paragraph_tuples) + + docs = cls._make_docs(nlp, raw_text, paragraph_tuples, + gold_preproc, noise_level=noise_level) + golds = cls._make_golds(docs, paragraph_tuples) + for doc, gold in zip(docs, golds): + if (not max_length) or len(doc) < max_length: + yield doc, gold + + @classmethod + def _make_docs(cls, nlp, raw_text, paragraph_tuples, gold_preproc, + noise_level=0.0): + if raw_text is not None: + raw_text = add_noise(raw_text, noise_level) + return [nlp.make_doc(raw_text)] + else: + return [Doc(nlp.vocab, words=add_noise(sent_tuples[1], noise_level)) + for (sent_tuples, brackets) in paragraph_tuples] + + @classmethod + def _make_golds(cls, docs, paragraph_tuples): + assert len(docs) == len(paragraph_tuples) + if len(docs) == 1: + return [GoldParse.from_annot_tuples(docs[0], paragraph_tuples[0][0])] + else: + return [GoldParse.from_annot_tuples(doc, sent_tuples) + for doc, (sent_tuples, brackets) in zip(docs, paragraph_tuples)] + + @staticmethod + def walk_corpus(path): + if not path.is_dir(): + return [path] + paths = [path] + locs = [] + seen = set() + for path in paths: + if str(path) in seen: + continue + seen.add(str(path)) + if path.parts[-1].startswith('.'): + continue + elif path.is_dir(): + paths.extend(path.iterdir()) + elif path.parts[-1].endswith('.json'): + locs.append(path) + return locs + + +def add_noise(orig, noise_level): + if random.random() >= noise_level: + return orig + elif type(orig) == list: + corrupted = [_corrupt(word, noise_level) for word in orig] + corrupted = [w for w in corrupted if w] + return corrupted + else: + return ''.join(_corrupt(c, noise_level) for c in orig) + + +def _corrupt(c, noise_level): + if random.random() >= noise_level: + return c + elif c == ' ': + return '\n' + elif c == '\n': + return ' ' + elif c in ['.', "'", "!", "?"]: + return '' + else: + return c.lower() + + +def read_json_file(loc, docs_filter=None, limit=None): loc = ensure_path(loc) if loc.is_dir(): for filename in loc.iterdir(): @@ -173,16 +332,14 @@ def read_json_file(loc, docs_filter=None, make_supertags=True, limit=None): if labels[-1].lower() == 'root': labels[-1] = 'ROOT' ner.append(token.get('ner', '-')) - if make_supertags: - tags[-1] = '-'.join((tags[-1], labels[-1], ner[-1])) sents.append([ [ids, words, tags, heads, labels, ner], - sent.get('brackets', [])]) + sent.get('brackets', [])]) if sents: yield [paragraph.get('raw', None), sents] -def _iob_to_biluo(tags): +def iob_to_biluo(tags): out = [] curr_label = None tags = list(tags) @@ -224,26 +381,25 @@ cdef class GoldParse: make_projective=make_projective) def __init__(self, doc, annot_tuples=None, words=None, tags=None, heads=None, - deps=None, entities=None, make_projective=False): - """ - Create a GoldParse. + deps=None, entities=None, make_projective=False, + cats=tuple()): + """Create a GoldParse. - Arguments: - doc (Doc): - The document the annotations refer to. - words: - A sequence of unicode word strings. - tags: - A sequence of strings, representing tag annotations. - heads: - A sequence of integers, representing syntactic head offsets. - deps: - A sequence of strings, representing the syntactic relation types. - entities: - A sequence of named entity annotations, either as BILUO tag strings, - or as (start_char, end_char, label) tuples, representing the entity - positions. - Returns (GoldParse): The newly constructed object. + doc (Doc): The document the annotations refer to. + words (iterable): A sequence of unicode word strings. + tags (iterable): A sequence of strings, representing tag annotations. + heads (iterable): A sequence of integers, representing syntactic head offsets. + deps (iterable): A sequence of strings, representing the syntactic relation types. + entities (iterable): A sequence of named entity annotations, either as + BILUO tag strings, or as `(start_char, end_char, label)` tuples, + representing the entity positions. + cats (iterable): A sequence of labels for text classification. Each + label may be a string or an int, or a `(start_char, end_char, label)` + tuple, indicating that the label is applied to only part of the + document (usually a sentence). Unlike entity annotations, label + annotations can overlap, i.e. a single word can be covered by + multiple labelled spans. + RETURNS (GoldParse): The newly constructed object. """ if words is None: words = [token.text for token in doc] @@ -268,9 +424,11 @@ cdef class GoldParse: # These are filled by the tagger/parser/entity recogniser self.c.tags = self.mem.alloc(len(doc), sizeof(int)) self.c.heads = self.mem.alloc(len(doc), sizeof(int)) - self.c.labels = self.mem.alloc(len(doc), sizeof(int)) + self.c.labels = self.mem.alloc(len(doc), sizeof(attr_t)) + self.c.has_dep = self.mem.alloc(len(doc), sizeof(int)) self.c.ner = self.mem.alloc(len(doc), sizeof(Transition)) + self.cats = list(cats) self.words = [None] * len(doc) self.tags = [None] * len(doc) self.heads = [None] * len(doc) @@ -295,7 +453,10 @@ cdef class GoldParse: else: self.words[i] = words[gold_i] self.tags[i] = tags[gold_i] - self.heads[i] = self.gold_to_cand[heads[gold_i]] + if heads[gold_i] is None: + self.heads[i] = None + else: + self.heads[i] = self.gold_to_cand[heads[gold_i]] self.labels[i] = deps[gold_i] self.ner[i] = entities[gold_i] @@ -304,59 +465,49 @@ cdef class GoldParse: raise Exception("Cycle found: %s" % cycle) if make_projective: - proj_heads,_ = nonproj.PseudoProjectivity.projectivize(self.heads, self.labels) + proj_heads,_ = nonproj.projectivize(self.heads, self.labels) self.heads = proj_heads def __len__(self): - """ - Get the number of gold-standard tokens. + """Get the number of gold-standard tokens. - Returns (int): The number of gold-standard tokens. + RETURNS (int): The number of gold-standard tokens. """ return self.length @property def is_projective(self): - """ - Whether the provided syntactic annotations form a projective dependency - tree. + """Whether the provided syntactic annotations form a projective + dependency tree. """ return not nonproj.is_nonproj_tree(self.heads) -def biluo_tags_from_offsets(doc, entities): - """ - Encode labelled spans into per-token tags, using the Begin/In/Last/Unit/Out - scheme (biluo). +def biluo_tags_from_offsets(doc, entities, missing='O'): + """Encode labelled spans into per-token tags, using the Begin/In/Last/Unit/Out + scheme (BILUO). - Arguments: - doc (Doc): - The document that the entity offsets refer to. The output tags will - refer to the token boundaries within the document. + doc (Doc): The document that the entity offsets refer to. The output tags + will refer to the token boundaries within the document. + entities (iterable): A sequence of `(start, end, label)` triples. `start` and + `end` should be character-offset integers denoting the slice into the + original string. - entities (sequence): - A sequence of (start, end, label) triples. start and end should be - character-offset integers denoting the slice into the original string. + RETURNS (list): A list of unicode strings, describing the tags. Each tag + string will be of the form either "", "O" or "{action}-{label}", where + action is one of "B", "I", "L", "U". The string "-" is used where the + entity offsets don't align with the tokenization in the `Doc` object. The + training algorithm will view these as missing values. "O" denotes a + non-entity token. "B" denotes the beginning of a multi-token entity, + "I" the inside of an entity of three or more tokens, and "L" the end + of an entity of two or more tokens. "U" denotes a single-token entity. - Returns: - tags (list): - A list of unicode strings, describing the tags. Each tag string will - be of the form either "", "O" or "{action}-{label}", where action is one - of "B", "I", "L", "U". The string "-" is used where the entity - offsets don't align with the tokenization in the Doc object. The - training algorithm will view these as missing values. "O" denotes - a non-entity token. "B" denotes the beginning of a multi-token entity, - "I" the inside of an entity of three or more tokens, and "L" the end - of an entity of two or more tokens. "U" denotes a single-token entity. - - Example: - text = 'I like London.' - entities = [(len('I like '), len('I like London'), 'LOC')] - doc = nlp.tokenizer(text) - - tags = biluo_tags_from_offsets(doc, entities) - - assert tags == ['O', 'O', 'U-LOC', 'O'] + EXAMPLE: + >>> text = 'I like London.' + >>> entities = [(len('I like '), len('I like London'), 'LOC')] + >>> doc = nlp.tokenizer(text) + >>> tags = biluo_tags_from_offsets(doc, entities) + >>> assert tags == ['O', 'O', 'U-LOC', 'O'] """ starts = {token.idx: token.i for token in doc} ends = {token.idx+len(token): token.i for token in doc} @@ -384,7 +535,7 @@ def biluo_tags_from_offsets(doc, entities): if i in entity_chars: break else: - biluo[token.i] = 'O' + biluo[token.i] = missing return biluo diff --git a/spacy/lang/bn/__init__.py b/spacy/lang/bn/__init__.py index cb748085b..c2cf12f12 100644 --- a/spacy/lang/bn/__init__.py +++ b/spacy/lang/bn/__init__.py @@ -13,21 +13,23 @@ from ...attrs import LANG from ...util import update_exc +class BengaliDefaults(Language.Defaults): + lex_attr_getters = dict(Language.Defaults.lex_attr_getters) + lex_attr_getters[LANG] = lambda text: 'bn' + + tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) + tag_map = TAG_MAP + stop_words = STOP_WORDS + lemma_rules = LEMMA_RULES + + prefixes = tuple(TOKENIZER_PREFIXES) + suffixes = tuple(TOKENIZER_SUFFIXES) + infixes = tuple(TOKENIZER_INFIXES) + + class Bengali(Language): lang = 'bn' - - class Defaults(Language.Defaults): - lex_attr_getters = dict(Language.Defaults.lex_attr_getters) - lex_attr_getters[LANG] = lambda text: 'bn' - - tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) - tag_map = TAG_MAP - stop_words = STOP_WORDS - lemma_rules = LEMMA_RULES - - prefixes = tuple(TOKENIZER_PREFIXES) - suffixes = tuple(TOKENIZER_SUFFIXES) - infixes = tuple(TOKENIZER_INFIXES) + Defaults = BengaliDefaults __all__ = ['Bengali'] diff --git a/spacy/lang/bn/punctuation.py b/spacy/lang/bn/punctuation.py index 66b7d967c..96485dd55 100644 --- a/spacy/lang/bn/punctuation.py +++ b/spacy/lang/bn/punctuation.py @@ -1,8 +1,8 @@ # coding: utf8 from __future__ import unicode_literals -from ..char_classes import LIST_PUNCT, LIST_ELLIPSES, LIST_QUOTES, UNITS -from ..char_classes import ALPHA_LOWER, ALPHA_UPPER, ALPHA, HYPHENS, QUOTES +from ..char_classes import LIST_PUNCT, LIST_ELLIPSES, LIST_QUOTES, LIST_ICONS +from ..char_classes import ALPHA_LOWER, ALPHA_UPPER, ALPHA, HYPHENS, QUOTES, UNITS _currency = r"\$|¢|£|€|¥|฿|৳" @@ -10,16 +10,16 @@ _quotes = QUOTES.replace("'", '') _list_punct = LIST_PUNCT + '। ॥'.strip().split() -_prefixes = ([r'\+'] + _list_punct + LIST_ELLIPSES + LIST_QUOTES) +_prefixes = ([r'\+'] + _list_punct + LIST_ELLIPSES + LIST_QUOTES + LIST_ICONS) -_suffixes = (_list_punct + LIST_ELLIPSES + LIST_QUOTES + +_suffixes = (_list_punct + LIST_ELLIPSES + LIST_QUOTES + LIST_ICONS + [r'(?<=[0-9])\+', r'(?<=°[FfCcKk])\.', r'(?<=[0-9])(?:{})'.format(_currency), r'(?<=[0-9])(?:{})'.format(UNITS), r'(?<=[{}(?:{})])\.'.format('|'.join([ALPHA_LOWER, r'%²\-\)\]\+', QUOTES]), _currency)]) -_infixes = (LIST_ELLIPSES + +_infixes = (LIST_ELLIPSES + LIST_ICONS + [r'(?<=[{}])\.(?=[{}])'.format(ALPHA_LOWER, ALPHA_UPPER), r'(?<=[{a}]),(?=[{a}])'.format(a=ALPHA), r'(?<=[{a}"])[:<>=](?=[{a}])'.format(a=ALPHA), diff --git a/spacy/lang/char_classes.py b/spacy/lang/char_classes.py index 5b81eddde..bec685646 100644 --- a/spacy/lang/char_classes.py +++ b/spacy/lang/char_classes.py @@ -20,7 +20,6 @@ _upper = [_latin_upper] _lower = [_latin_lower] _uncased = [_bengali, _hebrew] - ALPHA = merge_char_classes(_upper + _lower + _uncased) ALPHA_LOWER = merge_char_classes(_lower + _uncased) ALPHA_UPPER = merge_char_classes(_upper + _uncased) @@ -33,13 +32,14 @@ _currency = r'\$ £ € ¥ ฿ US\$ C\$ A\$' _punct = r'… , : ; \! \? ¿ ¡ \( \) \[ \] \{ \} < > _ # \* &' _quotes = r'\' \'\' " ” “ `` ` ‘ ´ ‚ , „ » «' _hyphens = '- – — -- ---' - +_other_symbols = r'[\p{So}]' UNITS = merge_chars(_units) CURRENCY = merge_chars(_currency) QUOTES = merge_chars(_quotes) PUNCT = merge_chars(_punct) HYPHENS = merge_chars(_hyphens) +ICONS = _other_symbols LIST_UNITS = split_chars(_units) LIST_CURRENCY = split_chars(_currency) @@ -47,3 +47,4 @@ LIST_QUOTES = split_chars(_quotes) LIST_PUNCT = split_chars(_punct) LIST_HYPHENS = split_chars(_hyphens) LIST_ELLIPSES = [r'\.\.+', '…'] +LIST_ICONS = [_other_symbols] diff --git a/spacy/lang/da/__init__.py b/spacy/lang/da/__init__.py index 9efe21fb5..99babdc2c 100644 --- a/spacy/lang/da/__init__.py +++ b/spacy/lang/da/__init__.py @@ -5,20 +5,24 @@ from .tokenizer_exceptions import TOKENIZER_EXCEPTIONS from .stop_words import STOP_WORDS from ..tokenizer_exceptions import BASE_EXCEPTIONS +from ..norm_exceptions import BASE_NORMS from ...language import Language -from ...attrs import LANG -from ...util import update_exc +from ...attrs import LANG, NORM +from ...util import update_exc, add_lookups + + +class DanishDefaults(Language.Defaults): + lex_attr_getters = dict(Language.Defaults.lex_attr_getters) + lex_attr_getters[LANG] = lambda text: 'da' + lex_attr_getters[NORM] = add_lookups(Language.Defaults.lex_attr_getters[NORM], BASE_NORMS) + + tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) + stop_words = set(STOP_WORDS) class Danish(Language): lang = 'da' - - class Defaults(Language.Defaults): - lex_attr_getters = dict(Language.Defaults.lex_attr_getters) - lex_attr_getters[LANG] = lambda text: 'da' - - tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) - stop_words = set(STOP_WORDS) + Defaults = DanishDefaults __all__ = ['Danish'] diff --git a/spacy/lang/de/__init__.py b/spacy/lang/de/__init__.py index 7a44b7485..b8a7580a0 100644 --- a/spacy/lang/de/__init__.py +++ b/spacy/lang/de/__init__.py @@ -2,33 +2,39 @@ from __future__ import unicode_literals from .tokenizer_exceptions import TOKENIZER_EXCEPTIONS +from .norm_exceptions import NORM_EXCEPTIONS from .tag_map import TAG_MAP from .stop_words import STOP_WORDS from .lemmatizer import LOOKUP from .syntax_iterators import SYNTAX_ITERATORS from ..tokenizer_exceptions import BASE_EXCEPTIONS +from ..norm_exceptions import BASE_NORMS from ...language import Language from ...lemmatizerlookup import Lemmatizer -from ...attrs import LANG -from ...util import update_exc +from ...attrs import LANG, NORM +from ...util import update_exc, add_lookups + + +class GermanDefaults(Language.Defaults): + lex_attr_getters = dict(Language.Defaults.lex_attr_getters) + lex_attr_getters[LANG] = lambda text: 'de' + lex_attr_getters[NORM] = add_lookups(Language.Defaults.lex_attr_getters[NORM], + NORM_EXCEPTIONS, BASE_NORMS) + + tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) + tag_map = dict(TAG_MAP) + stop_words = set(STOP_WORDS) + syntax_iterators = dict(SYNTAX_ITERATORS) + + @classmethod + def create_lemmatizer(cls, nlp=None): + return Lemmatizer(LOOKUP) class German(Language): lang = 'de' - - class Defaults(Language.Defaults): - lex_attr_getters = dict(Language.Defaults.lex_attr_getters) - lex_attr_getters[LANG] = lambda text: 'de' - - tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) - tag_map = dict(TAG_MAP) - stop_words = set(STOP_WORDS) - syntax_iterators = dict(SYNTAX_ITERATORS) - - @classmethod - def create_lemmatizer(cls, nlp=None): - return Lemmatizer(LOOKUP) + Defaults = GermanDefaults __all__ = ['German'] diff --git a/spacy/lang/de/norm_exceptions.py b/spacy/lang/de/norm_exceptions.py new file mode 100644 index 000000000..6116aa9be --- /dev/null +++ b/spacy/lang/de/norm_exceptions.py @@ -0,0 +1,17 @@ +# coding: utf8 +from __future__ import unicode_literals + +# Here we only want to include the absolute most common words. Otherwise, +# this list would get impossibly long for German – especially considering the +# old vs. new spelling rules, and all possible cases. + + +_exc = { + "daß": "dass" +} + + +NORM_EXCEPTIONS = {} + +for string, norm in _exc.items(): + NORM_EXCEPTIONS[string.title()] = norm diff --git a/spacy/lang/de/syntax_iterators.py b/spacy/lang/de/syntax_iterators.py index ab750989e..e5dcbf1ff 100644 --- a/spacy/lang/de/syntax_iterators.py +++ b/spacy/lang/de/syntax_iterators.py @@ -15,9 +15,9 @@ def noun_chunks(obj): # and not just "eine Tasse", same for "das Thema Familie". labels = ['sb', 'oa', 'da', 'nk', 'mo', 'ag', 'ROOT', 'root', 'cj', 'pd', 'og', 'app'] doc = obj.doc # Ensure works on both Doc and Span. - np_label = doc.vocab.strings['NP'] - np_deps = set(doc.vocab.strings[label] for label in labels) - close_app = doc.vocab.strings['nk'] + np_label = doc.vocab.strings.add('NP') + np_deps = set(doc.vocab.strings.add(label) for label in labels) + close_app = doc.vocab.strings.add('nk') rbracket = 0 for i, word in enumerate(obj): diff --git a/spacy/lang/de/tokenizer_exceptions.py b/spacy/lang/de/tokenizer_exceptions.py index 080311f4e..184d88104 100644 --- a/spacy/lang/de/tokenizer_exceptions.py +++ b/spacy/lang/de/tokenizer_exceptions.py @@ -8,7 +8,7 @@ from ...deprecated import PRON_LEMMA _exc = { "auf'm": [ {ORTH: "auf", LEMMA: "auf"}, - {ORTH: "'m", LEMMA: "der", NORM: "dem" }], + {ORTH: "'m", LEMMA: "der", NORM: "dem"}], "du's": [ {ORTH: "du", LEMMA: PRON_LEMMA, TAG: "PPER"}, @@ -53,97 +53,97 @@ _exc = { for exc_data in [ - {ORTH: "'S", LEMMA: PRON_LEMMA, TAG: "PPER"}, - {ORTH: "'s", LEMMA: PRON_LEMMA, TAG: "PPER"}, - {ORTH: "S'", LEMMA: PRON_LEMMA, TAG: "PPER"}, - {ORTH: "s'", LEMMA: PRON_LEMMA, TAG: "PPER"}, + {ORTH: "'S", LEMMA: PRON_LEMMA, NORM: "'s", TAG: "PPER"}, + {ORTH: "'s", LEMMA: PRON_LEMMA, NORM: "'s", TAG: "PPER"}, + {ORTH: "S'", LEMMA: PRON_LEMMA, NORM: "'s", TAG: "PPER"}, + {ORTH: "s'", LEMMA: PRON_LEMMA, NORM: "'s", TAG: "PPER"}, {ORTH: "'n", LEMMA: "ein", NORM: "ein"}, {ORTH: "'ne", LEMMA: "eine", NORM: "eine"}, {ORTH: "'nen", LEMMA: "ein", NORM: "einen"}, {ORTH: "'nem", LEMMA: "ein", NORM: "einem"}, - {ORTH: "Abb.", LEMMA: "Abbildung"}, - {ORTH: "Abk.", LEMMA: "Abkürzung"}, - {ORTH: "Abt.", LEMMA: "Abteilung"}, - {ORTH: "Apr.", LEMMA: "April"}, - {ORTH: "Aug.", LEMMA: "August"}, - {ORTH: "Bd.", LEMMA: "Band"}, - {ORTH: "Betr.", LEMMA: "Betreff"}, - {ORTH: "Bf.", LEMMA: "Bahnhof"}, - {ORTH: "Bhf.", LEMMA: "Bahnhof"}, - {ORTH: "Bsp.", LEMMA: "Beispiel"}, - {ORTH: "Dez.", LEMMA: "Dezember"}, - {ORTH: "Di.", LEMMA: "Dienstag"}, - {ORTH: "Do.", LEMMA: "Donnerstag"}, - {ORTH: "Fa.", LEMMA: "Firma"}, - {ORTH: "Fam.", LEMMA: "Familie"}, - {ORTH: "Feb.", LEMMA: "Februar"}, - {ORTH: "Fr.", LEMMA: "Frau"}, - {ORTH: "Frl.", LEMMA: "Fräulein"}, - {ORTH: "Hbf.", LEMMA: "Hauptbahnhof"}, - {ORTH: "Hr.", LEMMA: "Herr"}, - {ORTH: "Hrn.", LEMMA: "Herr"}, - {ORTH: "Jan.", LEMMA: "Januar"}, - {ORTH: "Jh.", LEMMA: "Jahrhundert"}, - {ORTH: "Jhd.", LEMMA: "Jahrhundert"}, - {ORTH: "Jul.", LEMMA: "Juli"}, - {ORTH: "Jun.", LEMMA: "Juni"}, - {ORTH: "Mi.", LEMMA: "Mittwoch"}, - {ORTH: "Mio.", LEMMA: "Million"}, - {ORTH: "Mo.", LEMMA: "Montag"}, - {ORTH: "Mrd.", LEMMA: "Milliarde"}, - {ORTH: "Mrz.", LEMMA: "März"}, - {ORTH: "MwSt.", LEMMA: "Mehrwertsteuer"}, - {ORTH: "Mär.", LEMMA: "März"}, - {ORTH: "Nov.", LEMMA: "November"}, - {ORTH: "Nr.", LEMMA: "Nummer"}, - {ORTH: "Okt.", LEMMA: "Oktober"}, - {ORTH: "Orig.", LEMMA: "Original"}, - {ORTH: "Pkt.", LEMMA: "Punkt"}, - {ORTH: "Prof.", LEMMA: "Professor"}, - {ORTH: "Red.", LEMMA: "Redaktion"}, - {ORTH: "Sa.", LEMMA: "Samstag"}, - {ORTH: "Sep.", LEMMA: "September"}, - {ORTH: "Sept.", LEMMA: "September"}, - {ORTH: "So.", LEMMA: "Sonntag"}, - {ORTH: "Std.", LEMMA: "Stunde"}, - {ORTH: "Str.", LEMMA: "Straße"}, - {ORTH: "Tel.", LEMMA: "Telefon"}, - {ORTH: "Tsd.", LEMMA: "Tausend"}, - {ORTH: "Univ.", LEMMA: "Universität"}, - {ORTH: "abzgl.", LEMMA: "abzüglich"}, - {ORTH: "allg.", LEMMA: "allgemein"}, - {ORTH: "bspw.", LEMMA: "beispielsweise"}, - {ORTH: "bzgl.", LEMMA: "bezüglich"}, - {ORTH: "bzw.", LEMMA: "beziehungsweise"}, + {ORTH: "Abb.", LEMMA: "Abbildung", NORM: "Abbildung"}, + {ORTH: "Abk.", LEMMA: "Abkürzung", NORM: "Abkürzung"}, + {ORTH: "Abt.", LEMMA: "Abteilung", NORM: "Abteilung"}, + {ORTH: "Apr.", LEMMA: "April", NORM: "April"}, + {ORTH: "Aug.", LEMMA: "August", NORM: "August"}, + {ORTH: "Bd.", LEMMA: "Band", NORM: "Band"}, + {ORTH: "Betr.", LEMMA: "Betreff", NORM: "Betreff"}, + {ORTH: "Bf.", LEMMA: "Bahnhof", NORM: "Bahnhof"}, + {ORTH: "Bhf.", LEMMA: "Bahnhof", NORM: "Bahnhof"}, + {ORTH: "Bsp.", LEMMA: "Beispiel", NORM: "Beispiel"}, + {ORTH: "Dez.", LEMMA: "Dezember", NORM: "Dezember"}, + {ORTH: "Di.", LEMMA: "Dienstag", NORM: "Dienstag"}, + {ORTH: "Do.", LEMMA: "Donnerstag", NORM: "Donnerstag"}, + {ORTH: "Fa.", LEMMA: "Firma", NORM: "Firma"}, + {ORTH: "Fam.", LEMMA: "Familie", NORM: "Familie"}, + {ORTH: "Feb.", LEMMA: "Februar", NORM: "Februar"}, + {ORTH: "Fr.", LEMMA: "Frau", NORM: "Frau"}, + {ORTH: "Frl.", LEMMA: "Fräulein", NORM: "Fräulein"}, + {ORTH: "Hbf.", LEMMA: "Hauptbahnhof", NORM: "Hauptbahnhof"}, + {ORTH: "Hr.", LEMMA: "Herr", NORM: "Herr"}, + {ORTH: "Hrn.", LEMMA: "Herr", NORM: "Herrn"}, + {ORTH: "Jan.", LEMMA: "Januar", NORM: "Januar"}, + {ORTH: "Jh.", LEMMA: "Jahrhundert", NORM: "Jahrhundert"}, + {ORTH: "Jhd.", LEMMA: "Jahrhundert", NORM: "Jahrhundert"}, + {ORTH: "Jul.", LEMMA: "Juli", NORM: "Juli"}, + {ORTH: "Jun.", LEMMA: "Juni", NORM: "Juni"}, + {ORTH: "Mi.", LEMMA: "Mittwoch", NORM: "Mittwoch"}, + {ORTH: "Mio.", LEMMA: "Million", NORM: "Million"}, + {ORTH: "Mo.", LEMMA: "Montag", NORM: "Montag"}, + {ORTH: "Mrd.", LEMMA: "Milliarde", NORM: "Milliarde"}, + {ORTH: "Mrz.", LEMMA: "März", NORM: "März"}, + {ORTH: "MwSt.", LEMMA: "Mehrwertsteuer", NORM: "Mehrwertsteuer"}, + {ORTH: "Mär.", LEMMA: "März", NORM: "März"}, + {ORTH: "Nov.", LEMMA: "November", NORM: "November"}, + {ORTH: "Nr.", LEMMA: "Nummer", NORM: "Nummer"}, + {ORTH: "Okt.", LEMMA: "Oktober", NORM: "Oktober"}, + {ORTH: "Orig.", LEMMA: "Original", NORM: "Original"}, + {ORTH: "Pkt.", LEMMA: "Punkt", NORM: "Punkt"}, + {ORTH: "Prof.", LEMMA: "Professor", NORM: "Professor"}, + {ORTH: "Red.", LEMMA: "Redaktion", NORM: "Redaktion"}, + {ORTH: "Sa.", LEMMA: "Samstag", NORM: "Samstag"}, + {ORTH: "Sep.", LEMMA: "September", NORM: "September"}, + {ORTH: "Sept.", LEMMA: "September", NORM: "September"}, + {ORTH: "So.", LEMMA: "Sonntag", NORM: "Sonntag"}, + {ORTH: "Std.", LEMMA: "Stunde", NORM: "Stunde"}, + {ORTH: "Str.", LEMMA: "Straße", NORM: "Straße"}, + {ORTH: "Tel.", LEMMA: "Telefon", NORM: "Telefon"}, + {ORTH: "Tsd.", LEMMA: "Tausend", NORM: "Tausend"}, + {ORTH: "Univ.", LEMMA: "Universität", NORM: "Universität"}, + {ORTH: "abzgl.", LEMMA: "abzüglich", NORM: "abzüglich"}, + {ORTH: "allg.", LEMMA: "allgemein", NORM: "allgemein"}, + {ORTH: "bspw.", LEMMA: "beispielsweise", NORM: "beispielsweise"}, + {ORTH: "bzgl.", LEMMA: "bezüglich", NORM: "bezüglich"}, + {ORTH: "bzw.", LEMMA: "beziehungsweise", NORM: "beziehungsweise"}, {ORTH: "d.h.", LEMMA: "das heißt"}, - {ORTH: "dgl.", LEMMA: "dergleichen"}, - {ORTH: "ebd.", LEMMA: "ebenda"}, - {ORTH: "eigtl.", LEMMA: "eigentlich"}, - {ORTH: "engl.", LEMMA: "englisch"}, - {ORTH: "evtl.", LEMMA: "eventuell"}, - {ORTH: "frz.", LEMMA: "französisch"}, - {ORTH: "gegr.", LEMMA: "gegründet"}, - {ORTH: "ggf.", LEMMA: "gegebenenfalls"}, - {ORTH: "ggfs.", LEMMA: "gegebenenfalls"}, - {ORTH: "ggü.", LEMMA: "gegenüber"}, + {ORTH: "dgl.", LEMMA: "dergleichen", NORM: "dergleichen"}, + {ORTH: "ebd.", LEMMA: "ebenda", NORM: "ebenda"}, + {ORTH: "eigtl.", LEMMA: "eigentlich", NORM: "eigentlich"}, + {ORTH: "engl.", LEMMA: "englisch", NORM: "englisch"}, + {ORTH: "evtl.", LEMMA: "eventuell", NORM: "eventuell"}, + {ORTH: "frz.", LEMMA: "französisch", NORM: "französisch"}, + {ORTH: "gegr.", LEMMA: "gegründet", NORM: "gegründet"}, + {ORTH: "ggf.", LEMMA: "gegebenenfalls", NORM: "gegebenenfalls"}, + {ORTH: "ggfs.", LEMMA: "gegebenenfalls", NORM: "gegebenenfalls"}, + {ORTH: "ggü.", LEMMA: "gegenüber", NORM: "gegenüber"}, {ORTH: "i.O.", LEMMA: "in Ordnung"}, {ORTH: "i.d.R.", LEMMA: "in der Regel"}, - {ORTH: "incl.", LEMMA: "inklusive"}, - {ORTH: "inkl.", LEMMA: "inklusive"}, - {ORTH: "insb.", LEMMA: "insbesondere"}, - {ORTH: "kath.", LEMMA: "katholisch"}, - {ORTH: "lt.", LEMMA: "laut"}, - {ORTH: "max.", LEMMA: "maximal"}, - {ORTH: "min.", LEMMA: "minimal"}, - {ORTH: "mind.", LEMMA: "mindestens"}, - {ORTH: "mtl.", LEMMA: "monatlich"}, + {ORTH: "incl.", LEMMA: "inklusive", NORM: "inklusive"}, + {ORTH: "inkl.", LEMMA: "inklusive", NORM: "inklusive"}, + {ORTH: "insb.", LEMMA: "insbesondere", NORM: "insbesondere"}, + {ORTH: "kath.", LEMMA: "katholisch", NORM: "katholisch"}, + {ORTH: "lt.", LEMMA: "laut", NORM: "laut"}, + {ORTH: "max.", LEMMA: "maximal", NORM: "maximal"}, + {ORTH: "min.", LEMMA: "minimal", NORM: "minimal"}, + {ORTH: "mind.", LEMMA: "mindestens", NORM: "mindestens"}, + {ORTH: "mtl.", LEMMA: "monatlich", NORM: "monatlich"}, {ORTH: "n.Chr.", LEMMA: "nach Christus"}, - {ORTH: "orig.", LEMMA: "original"}, - {ORTH: "röm.", LEMMA: "römisch"}, + {ORTH: "orig.", LEMMA: "original", NORM: "original"}, + {ORTH: "röm.", LEMMA: "römisch", NORM: "römisch"}, {ORTH: "s.o.", LEMMA: "siehe oben"}, {ORTH: "sog.", LEMMA: "so genannt"}, {ORTH: "stellv.", LEMMA: "stellvertretend"}, - {ORTH: "tägl.", LEMMA: "täglich"}, + {ORTH: "tägl.", LEMMA: "täglich", NORM: "täglich"}, {ORTH: "u.U.", LEMMA: "unter Umständen"}, {ORTH: "u.s.w.", LEMMA: "und so weiter"}, {ORTH: "u.v.m.", LEMMA: "und vieles mehr"}, @@ -153,9 +153,9 @@ for exc_data in [ {ORTH: "v.Chr.", LEMMA: "vor Christus"}, {ORTH: "v.a.", LEMMA: "vor allem"}, {ORTH: "v.l.n.r.", LEMMA: "von links nach rechts"}, - {ORTH: "vgl.", LEMMA: "vergleiche"}, - {ORTH: "vllt.", LEMMA: "vielleicht"}, - {ORTH: "vlt.", LEMMA: "vielleicht"}, + {ORTH: "vgl.", LEMMA: "vergleiche", NORM: "vergleiche"}, + {ORTH: "vllt.", LEMMA: "vielleicht", NORM: "vielleicht"}, + {ORTH: "vlt.", LEMMA: "vielleicht", NORM: "vielleicht"}, {ORTH: "z.B.", LEMMA: "zum Beispiel"}, {ORTH: "z.Bsp.", LEMMA: "zum Beispiel"}, {ORTH: "z.T.", LEMMA: "zum Teil"}, @@ -163,7 +163,7 @@ for exc_data in [ {ORTH: "z.Zt.", LEMMA: "zur Zeit"}, {ORTH: "z.b.", LEMMA: "zum Beispiel"}, {ORTH: "zzgl.", LEMMA: "zuzüglich"}, - {ORTH: "österr.", LEMMA: "österreichisch"}]: + {ORTH: "österr.", LEMMA: "österreichisch", NORM: "österreichisch"}]: _exc[exc_data[ORTH]] = [dict(exc_data)] diff --git a/spacy/lang/en/__init__.py b/spacy/lang/en/__init__.py index 2d5314991..ec14fecd0 100644 --- a/spacy/lang/en/__init__.py +++ b/spacy/lang/en/__init__.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from .tokenizer_exceptions import TOKENIZER_EXCEPTIONS +from .norm_exceptions import NORM_EXCEPTIONS from .tag_map import TAG_MAP from .stop_words import STOP_WORDS from .lex_attrs import LEX_ATTRS @@ -10,27 +11,32 @@ from .lemmatizer import LEMMA_RULES, LEMMA_INDEX, LEMMA_EXC from .syntax_iterators import SYNTAX_ITERATORS from ..tokenizer_exceptions import BASE_EXCEPTIONS +from ..norm_exceptions import BASE_NORMS from ...language import Language -from ...attrs import LANG -from ...util import update_exc +from ...attrs import LANG, NORM +from ...util import update_exc, add_lookups + + +class EnglishDefaults(Language.Defaults): + lex_attr_getters = dict(Language.Defaults.lex_attr_getters) + lex_attr_getters.update(LEX_ATTRS) + lex_attr_getters[LANG] = lambda text: 'en' + lex_attr_getters[NORM] = add_lookups(Language.Defaults.lex_attr_getters[NORM], + BASE_NORMS, NORM_EXCEPTIONS) + + tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) + tag_map = dict(TAG_MAP) + stop_words = set(STOP_WORDS) + morph_rules = dict(MORPH_RULES) + lemma_rules = dict(LEMMA_RULES) + lemma_index = dict(LEMMA_INDEX) + lemma_exc = dict(LEMMA_EXC) + syntax_iterators = dict(SYNTAX_ITERATORS) class English(Language): lang = 'en' - - class Defaults(Language.Defaults): - lex_attr_getters = dict(Language.Defaults.lex_attr_getters) - lex_attr_getters[LANG] = lambda text: 'en' - lex_attr_getters.update(LEX_ATTRS) - - tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) - tag_map = dict(TAG_MAP) - stop_words = set(STOP_WORDS) - morph_rules = dict(MORPH_RULES) - lemma_rules = dict(LEMMA_RULES) - lemma_index = dict(LEMMA_INDEX) - lemma_exc = dict(LEMMA_EXC) - sytax_iterators = dict(SYNTAX_ITERATORS) + Defaults = EnglishDefaults __all__ = ['English'] diff --git a/spacy/lang/en/norm_exceptions.py b/spacy/lang/en/norm_exceptions.py new file mode 100644 index 000000000..49c8ef6ab --- /dev/null +++ b/spacy/lang/en/norm_exceptions.py @@ -0,0 +1,1761 @@ +# coding: utf8 +from __future__ import unicode_literals + + +_exc = { + # Slang and abbreviations + "cos": "because", + "cuz": "because", + "fav": "favorite", + "fave": "favorite", + "misc": "miscellaneous", + "plz": "please", + "pls": "please", + "thx": "thanks", + + # US vs. UK spelling + "accessorise": "accessorize", + "accessorised": "accessorized", + "accessorises": "accessorizes", + "accessorising": "accessorizing", + "acclimatisation": "acclimatization", + "acclimatise": "acclimatize", + "acclimatised": "acclimatized", + "acclimatises": "acclimatizes", + "acclimatising": "acclimatizing", + "accoutrements": "accouterments", + "aeon": "eon", + "aeons": "eons", + "aerogramme": "aerogram", + "aerogrammes": "aerograms", + "aeroplane": "airplane", + "aeroplanes ": "airplanes ", + "aesthete": "esthete", + "aesthetes": "esthetes", + "aesthetic": "esthetic", + "aesthetically": "esthetically", + "aesthetics": "esthetics", + "aetiology": "etiology", + "ageing": "aging", + "aggrandisement": "aggrandizement", + "agonise": "agonize", + "agonised": "agonized", + "agonises": "agonizes", + "agonising": "agonizing", + "agonisingly": "agonizingly", + "almanack": "almanac", + "almanacks": "almanacs", + "aluminium": "aluminum", + "amortisable": "amortizable", + "amortisation": "amortization", + "amortisations": "amortizations", + "amortise": "amortize", + "amortised": "amortized", + "amortises": "amortizes", + "amortising": "amortizing", + "amphitheatre": "amphitheater", + "amphitheatres": "amphitheaters", + "anaemia": "anemia", + "anaemic": "anemic", + "anaesthesia": "anesthesia", + "anaesthetic": "anesthetic", + "anaesthetics": "anesthetics", + "anaesthetise": "anesthetize", + "anaesthetised": "anesthetized", + "anaesthetises": "anesthetizes", + "anaesthetising": "anesthetizing", + "anaesthetist": "anesthetist", + "anaesthetists": "anesthetists", + "anaesthetize": "anesthetize", + "anaesthetized": "anesthetized", + "anaesthetizes": "anesthetizes", + "anaesthetizing": "anesthetizing", + "analogue": "analog", + "analogues": "analogs", + "analyse": "analyze", + "analysed": "analyzed", + "analyses": "analyzes", + "analysing": "analyzing", + "anglicise": "anglicize", + "anglicised": "anglicized", + "anglicises": "anglicizes", + "anglicising": "anglicizing", + "annualised": "annualized", + "antagonise": "antagonize", + "antagonised": "antagonized", + "antagonises": "antagonizes", + "antagonising": "antagonizing", + "apologise": "apologize", + "apologised": "apologized", + "apologises": "apologizes", + "apologising": "apologizing", + "appal": "appall", + "appals": "appalls", + "appetiser": "appetizer", + "appetisers": "appetizers", + "appetising": "appetizing", + "appetisingly": "appetizingly", + "arbour": "arbor", + "arbours": "arbors", + "archaeological": "archeological", + "archaeologically": "archeologically", + "archaeologist": "archeologist", + "archaeologists": "archeologists", + "archaeology": "archeology", + "ardour": "ardor", + "armour": "armor", + "armoured": "armored", + "armourer": "armorer", + "armourers": "armorers", + "armouries": "armories", + "armoury": "armory", + "artefact": "artifact", + "artefacts": "artifacts", + "authorise": "authorize", + "authorised": "authorized", + "authorises": "authorizes", + "authorising": "authorizing", + "axe": "ax", + "backpedalled": "backpedaled", + "backpedalling": "backpedaling", + "bannister": "banister", + "bannisters": "banisters", + "baptise": "baptize", + "baptised": "baptized", + "baptises": "baptizes", + "baptising": "baptizing", + "bastardise": "bastardize", + "bastardised": "bastardized", + "bastardises": "bastardizes", + "bastardising": "bastardizing", + "battleaxe": "battleax", + "baulk": "balk", + "baulked": "balked", + "baulking": "balking", + "baulks": "balks", + "bedevilled": "bedeviled", + "bedevilling": "bedeviling", + "behaviour": "behavior", + "behavioural": "behavioral", + "behaviourism": "behaviorism", + "behaviourist": "behaviorist", + "behaviourists": "behaviorists", + "behaviours": "behaviors", + "behove": "behoove", + "behoved": "behooved", + "behoves": "behooves", + "bejewelled": "bejeweled", + "belabour": "belabor", + "belaboured": "belabored", + "belabouring": "belaboring", + "belabours": "belabors", + "bevelled": "beveled", + "bevvies": "bevies", + "bevvy": "bevy", + "biassed": "biased", + "biassing": "biasing", + "bingeing": "binging", + "bougainvillaea": "bougainvillea", + "bougainvillaeas": "bougainvilleas", + "bowdlerise": "bowdlerize", + "bowdlerised": "bowdlerized", + "bowdlerises": "bowdlerizes", + "bowdlerising": "bowdlerizing", + "breathalyse": "breathalyze", + "breathalysed": "breathalyzed", + "breathalyser": "breathalyzer", + "breathalysers": "breathalyzers", + "breathalyses": "breathalyzes", + "breathalysing": "breathalyzing", + "brutalise": "brutalize", + "brutalised": "brutalized", + "brutalises": "brutalizes", + "brutalising": "brutalizing", + "buses": "busses", + "busing": "bussing", + "caesarean": "cesarean", + "caesareans": "cesareans", + "calibre": "caliber", + "calibres": "calibers", + "calliper": "caliper", + "callipers": "calipers", + "callisthenics": "calisthenics", + "canalise": "canalize", + "canalised": "canalized", + "canalises": "canalizes", + "canalising": "canalizing", + "cancellation": "cancelation", + "cancellations": "cancelations", + "cancelled": "canceled", + "cancelling": "canceling", + "candour": "candor", + "cannibalise": "cannibalize", + "cannibalised": "cannibalized", + "cannibalises": "cannibalizes", + "cannibalising": "cannibalizing", + "canonise": "canonize", + "canonised": "canonized", + "canonises": "canonizes", + "canonising": "canonizing", + "capitalise": "capitalize", + "capitalised": "capitalized", + "capitalises": "capitalizes", + "capitalising": "capitalizing", + "caramelise": "caramelize", + "caramelised": "caramelized", + "caramelises": "caramelizes", + "caramelising": "caramelizing", + "carbonise": "carbonize", + "carbonised": "carbonized", + "carbonises": "carbonizes", + "carbonising": "carbonizing", + "carolled": "caroled", + "carolling": "caroling", + "catalogue": "catalog", + "catalogued": "cataloged", + "catalogues": "catalogs", + "cataloguing": "cataloging", + "catalyse": "catalyze", + "catalysed": "catalyzed", + "catalyses": "catalyzes", + "catalysing": "catalyzing", + "categorise": "categorize", + "categorised": "categorized", + "categorises": "categorizes", + "categorising": "categorizing", + "cauterise": "cauterize", + "cauterised": "cauterized", + "cauterises": "cauterizes", + "cauterising": "cauterizing", + "cavilled": "caviled", + "cavilling": "caviling", + "centigramme": "centigram", + "centigrammes": "centigrams", + "centilitre": "centiliter", + "centilitres": "centiliters", + "centimetre": "centimeter", + "centimetres": "centimeters", + "centralise": "centralize", + "centralised": "centralized", + "centralises": "centralizes", + "centralising": "centralizing", + "centre": "center", + "centred": "centered", + "centrefold": "centerfold", + "centrefolds": "centerfolds", + "centrepiece": "centerpiece", + "centrepieces": "centerpieces", + "centres": "centers", + "channelled": "channeled", + "channelling": "channeling", + "characterise": "characterize", + "characterised": "characterized", + "characterises": "characterizes", + "characterising": "characterizing", + "cheque": "check", + "chequebook": "checkbook", + "chequebooks": "checkbooks", + "chequered": "checkered", + "cheques": "checks", + "chilli": "chili", + "chimaera": "chimera", + "chimaeras": "chimeras", + "chiselled": "chiseled", + "chiselling": "chiseling", + "circularise": "circularize", + "circularised": "circularized", + "circularises": "circularizes", + "circularising": "circularizing", + "civilise": "civilize", + "civilised": "civilized", + "civilises": "civilizes", + "civilising": "civilizing", + "clamour": "clamor", + "clamoured": "clamored", + "clamouring": "clamoring", + "clamours": "clamors", + "clangour": "clangor", + "clarinettist": "clarinetist", + "clarinettists": "clarinetists", + "collectivise": "collectivize", + "collectivised": "collectivized", + "collectivises": "collectivizes", + "collectivising": "collectivizing", + "colonisation": "colonization", + "colonise": "colonize", + "colonised": "colonized", + "coloniser": "colonizer", + "colonisers": "colonizers", + "colonises": "colonizes", + "colonising": "colonizing", + "colour": "color", + "colourant": "colorant", + "colourants": "colorants", + "coloured": "colored", + "coloureds": "coloreds", + "colourful": "colorful", + "colourfully": "colorfully", + "colouring": "coloring", + "colourize": "colorize", + "colourized": "colorized", + "colourizes": "colorizes", + "colourizing": "colorizing", + "colourless": "colorless", + "colours": "colors", + "commercialise": "commercialize", + "commercialised": "commercialized", + "commercialises": "commercializes", + "commercialising": "commercializing", + "compartmentalise": "compartmentalize", + "compartmentalised": "compartmentalized", + "compartmentalises": "compartmentalizes", + "compartmentalising": "compartmentalizing", + "computerise": "computerize", + "computerised": "computerized", + "computerises": "computerizes", + "computerising": "computerizing", + "conceptualise": "conceptualize", + "conceptualised": "conceptualized", + "conceptualises": "conceptualizes", + "conceptualising": "conceptualizing", + "connexion": "connection", + "connexions": "connections", + "contextualise": "contextualize", + "contextualised": "contextualized", + "contextualises": "contextualizes", + "contextualising": "contextualizing", + "cosier": "cozier", + "cosies": "cozies", + "cosiest": "coziest", + "cosily": "cozily", + "cosiness": "coziness", + "cosy": "cozy", + "councillor": "councilor", + "councillors": "councilors", + "counselled": "counseled", + "counselling": "counseling", + "counsellor": "counselor", + "counsellors": "counselors", + "crenellated": "crenelated", + "criminalise": "criminalize", + "criminalised": "criminalized", + "criminalises": "criminalizes", + "criminalising": "criminalizing", + "criticise": "criticize", + "criticised": "criticized", + "criticises": "criticizes", + "criticising": "criticizing", + "crueller": "crueler", + "cruellest": "cruelest", + "crystallisation": "crystallization", + "crystallise": "crystallize", + "crystallised": "crystallized", + "crystallises": "crystallizes", + "crystallising": "crystallizing", + "cudgelled": "cudgeled", + "cudgelling": "cudgeling", + "customise": "customize", + "customised": "customized", + "customises": "customizes", + "customising": "customizing", + "cypher": "cipher", + "cyphers": "ciphers", + "decentralisation": "decentralization", + "decentralise": "decentralize", + "decentralised": "decentralized", + "decentralises": "decentralizes", + "decentralising": "decentralizing", + "decriminalisation": "decriminalization", + "decriminalise": "decriminalize", + "decriminalised": "decriminalized", + "decriminalises": "decriminalizes", + "decriminalising": "decriminalizing", + "defence": "defense", + "defenceless": "defenseless", + "defences": "defenses", + "dehumanisation": "dehumanization", + "dehumanise": "dehumanize", + "dehumanised": "dehumanized", + "dehumanises": "dehumanizes", + "dehumanising": "dehumanizing", + "demeanour": "demeanor", + "demilitarisation": "demilitarization", + "demilitarise": "demilitarize", + "demilitarised": "demilitarized", + "demilitarises": "demilitarizes", + "demilitarising": "demilitarizing", + "demobilisation": "demobilization", + "demobilise": "demobilize", + "demobilised": "demobilized", + "demobilises": "demobilizes", + "demobilising": "demobilizing", + "democratisation": "democratization", + "democratise": "democratize", + "democratised": "democratized", + "democratises": "democratizes", + "democratising": "democratizing", + "demonise": "demonize", + "demonised": "demonized", + "demonises": "demonizes", + "demonising": "demonizing", + "demoralisation": "demoralization", + "demoralise": "demoralize", + "demoralised": "demoralized", + "demoralises": "demoralizes", + "demoralising": "demoralizing", + "denationalisation": "denationalization", + "denationalise": "denationalize", + "denationalised": "denationalized", + "denationalises": "denationalizes", + "denationalising": "denationalizing", + "deodorise": "deodorize", + "deodorised": "deodorized", + "deodorises": "deodorizes", + "deodorising": "deodorizing", + "depersonalise": "depersonalize", + "depersonalised": "depersonalized", + "depersonalises": "depersonalizes", + "depersonalising": "depersonalizing", + "deputise": "deputize", + "deputised": "deputized", + "deputises": "deputizes", + "deputising": "deputizing", + "desensitisation": "desensitization", + "desensitise": "desensitize", + "desensitised": "desensitized", + "desensitises": "desensitizes", + "desensitising": "desensitizing", + "destabilisation": "destabilization", + "destabilise": "destabilize", + "destabilised": "destabilized", + "destabilises": "destabilizes", + "destabilising": "destabilizing", + "dialled": "dialed", + "dialling": "dialing", + "dialogue": "dialog", + "dialogues": "dialogs", + "diarrhoea": "diarrhea", + "digitise": "digitize", + "digitised": "digitized", + "digitises": "digitizes", + "digitising": "digitizing", + "disc": "disk", + "discolour": "discolor", + "discoloured": "discolored", + "discolouring": "discoloring", + "discolours": "discolors", + "discs": "disks", + "disembowelled": "disemboweled", + "disembowelling": "disemboweling", + "disfavour": "disfavor", + "dishevelled": "disheveled", + "dishonour": "dishonor", + "dishonourable": "dishonorable", + "dishonourably": "dishonorably", + "dishonoured": "dishonored", + "dishonouring": "dishonoring", + "dishonours": "dishonors", + "disorganisation": "disorganization", + "disorganised": "disorganized", + "distil": "distill", + "distils": "distills", + "dramatisation": "dramatization", + "dramatisations": "dramatizations", + "dramatise": "dramatize", + "dramatised": "dramatized", + "dramatises": "dramatizes", + "dramatising": "dramatizing", + "draught": "draft", + "draughtboard": "draftboard", + "draughtboards": "draftboards", + "draughtier": "draftier", + "draughtiest": "draftiest", + "draughts": "drafts", + "draughtsman": "draftsman", + "draughtsmanship": "draftsmanship", + "draughtsmen": "draftsmen", + "draughtswoman": "draftswoman", + "draughtswomen": "draftswomen", + "draughty": "drafty", + "drivelled": "driveled", + "drivelling": "driveling", + "duelled": "dueled", + "duelling": "dueling", + "economise": "economize", + "economised": "economized", + "economises": "economizes", + "economising": "economizing", + "edoema": "edema ", + "editorialise": "editorialize", + "editorialised": "editorialized", + "editorialises": "editorializes", + "editorialising": "editorializing", + "empathise": "empathize", + "empathised": "empathized", + "empathises": "empathizes", + "empathising": "empathizing", + "emphasise": "emphasize", + "emphasised": "emphasized", + "emphasises": "emphasizes", + "emphasising": "emphasizing", + "enamelled": "enameled", + "enamelling": "enameling", + "enamoured": "enamored", + "encyclopaedia": "encyclopedia", + "encyclopaedias": "encyclopedias", + "encyclopaedic": "encyclopedic", + "endeavour": "endeavor", + "endeavoured": "endeavored", + "endeavouring": "endeavoring", + "endeavours": "endeavors", + "energise": "energize", + "energised": "energized", + "energises": "energizes", + "energising": "energizing", + "enrol": "enroll", + "enrols": "enrolls", + "enthral": "enthrall", + "enthrals": "enthralls", + "epaulette": "epaulet", + "epaulettes": "epaulets", + "epicentre": "epicenter", + "epicentres": "epicenters", + "epilogue": "epilog", + "epilogues": "epilogs", + "epitomise": "epitomize", + "epitomised": "epitomized", + "epitomises": "epitomizes", + "epitomising": "epitomizing", + "equalisation": "equalization", + "equalise": "equalize", + "equalised": "equalized", + "equaliser": "equalizer", + "equalisers": "equalizers", + "equalises": "equalizes", + "equalising": "equalizing", + "eulogise": "eulogize", + "eulogised": "eulogized", + "eulogises": "eulogizes", + "eulogising": "eulogizing", + "evangelise": "evangelize", + "evangelised": "evangelized", + "evangelises": "evangelizes", + "evangelising": "evangelizing", + "exorcise": "exorcize", + "exorcised": "exorcized", + "exorcises": "exorcizes", + "exorcising": "exorcizing", + "extemporisation": "extemporization", + "extemporise": "extemporize", + "extemporised": "extemporized", + "extemporises": "extemporizes", + "extemporising": "extemporizing", + "externalisation": "externalization", + "externalisations": "externalizations", + "externalise": "externalize", + "externalised": "externalized", + "externalises": "externalizes", + "externalising": "externalizing", + "factorise": "factorize", + "factorised": "factorized", + "factorises": "factorizes", + "factorising": "factorizing", + "faecal": "fecal", + "faeces": "feces", + "familiarisation": "familiarization", + "familiarise": "familiarize", + "familiarised": "familiarized", + "familiarises": "familiarizes", + "familiarising": "familiarizing", + "fantasise": "fantasize", + "fantasised": "fantasized", + "fantasises": "fantasizes", + "fantasising": "fantasizing", + "favour": "favor", + "favourable": "favorable", + "favourably": "favorably", + "favoured": "favored", + "favouring": "favoring", + "favourite": "favorite", + "favourites": "favorites", + "favouritism": "favoritism", + "favours": "favors", + "feminise": "feminize", + "feminised": "feminized", + "feminises": "feminizes", + "feminising": "feminizing", + "fertilisation": "fertilization", + "fertilise": "fertilize", + "fertilised": "fertilized", + "fertiliser": "fertilizer", + "fertilisers": "fertilizers", + "fertilises": "fertilizes", + "fertilising": "fertilizing", + "fervour": "fervor", + "fibre": "fiber", + "fibreglass": "fiberglass", + "fibres": "fibers", + "fictionalisation": "fictionalization", + "fictionalisations": "fictionalizations", + "fictionalise": "fictionalize", + "fictionalised": "fictionalized", + "fictionalises": "fictionalizes", + "fictionalising": "fictionalizing", + "fillet": "filet", + "filleted ": "fileted ", + "filleting": "fileting", + "fillets ": "filets ", + "finalisation": "finalization", + "finalise": "finalize", + "finalised": "finalized", + "finalises": "finalizes", + "finalising": "finalizing", + "flautist": "flutist", + "flautists": "flutists", + "flavour": "flavor", + "flavoured": "flavored", + "flavouring": "flavoring", + "flavourings": "flavorings", + "flavourless": "flavorless", + "flavours": "flavors", + "flavoursome": "flavorsome", + "flyer / flier ": "flier / flyer ", + "foetal": "fetal", + "foetid": "fetid", + "foetus": "fetus", + "foetuses": "fetuses", + "formalisation": "formalization", + "formalise": "formalize", + "formalised": "formalized", + "formalises": "formalizes", + "formalising": "formalizing", + "fossilisation": "fossilization", + "fossilise": "fossilize", + "fossilised": "fossilized", + "fossilises": "fossilizes", + "fossilising": "fossilizing", + "fraternisation": "fraternization", + "fraternise": "fraternize", + "fraternised": "fraternized", + "fraternises": "fraternizes", + "fraternising": "fraternizing", + "fulfil": "fulfill", + "fulfilment": "fulfillment", + "fulfils": "fulfills", + "funnelled": "funneled", + "funnelling": "funneling", + "galvanise": "galvanize", + "galvanised": "galvanized", + "galvanises": "galvanizes", + "galvanising": "galvanizing", + "gambolled": "gamboled", + "gambolling": "gamboling", + "gaol": "jail", + "gaolbird": "jailbird", + "gaolbirds": "jailbirds", + "gaolbreak": "jailbreak", + "gaolbreaks": "jailbreaks", + "gaoled": "jailed", + "gaoler": "jailer", + "gaolers": "jailers", + "gaoling": "jailing", + "gaols": "jails", + "gases": "gasses", + "gauge": "gage", + "gauged": "gaged", + "gauges": "gages", + "gauging": "gaging", + "generalisation": "generalization", + "generalisations": "generalizations", + "generalise": "generalize", + "generalised": "generalized", + "generalises": "generalizes", + "generalising": "generalizing", + "ghettoise": "ghettoize", + "ghettoised": "ghettoized", + "ghettoises": "ghettoizes", + "ghettoising": "ghettoizing", + "gipsies": "gypsies", + "glamorise": "glamorize", + "glamorised": "glamorized", + "glamorises": "glamorizes", + "glamorising": "glamorizing", + "glamour": "glamor", + "globalisation": "globalization", + "globalise": "globalize", + "globalised": "globalized", + "globalises": "globalizes", + "globalising": "globalizing", + "glueing ": "gluing ", + "goitre": "goiter", + "goitres": "goiters", + "gonorrhoea": "gonorrhea", + "gramme": "gram", + "grammes": "grams", + "gravelled": "graveled", + "grey": "gray", + "greyed": "grayed", + "greying": "graying", + "greyish": "grayish", + "greyness": "grayness", + "greys": "grays", + "grovelled": "groveled", + "grovelling": "groveling", + "groyne": "groin", + "groynes ": "groins", + "gruelling": "grueling", + "gruellingly": "gruelingly", + "gryphon": "griffin", + "gryphons": "griffins", + "gynaecological": "gynecological", + "gynaecologist": "gynecologist", + "gynaecologists": "gynecologists", + "gynaecology": "gynecology", + "haematological": "hematological", + "haematologist": "hematologist", + "haematologists": "hematologists", + "haematology": "hematology", + "haemoglobin": "hemoglobin", + "haemophilia": "hemophilia", + "haemophiliac": "hemophiliac", + "haemophiliacs": "hemophiliacs", + "haemorrhage": "hemorrhage", + "haemorrhaged": "hemorrhaged", + "haemorrhages": "hemorrhages", + "haemorrhaging": "hemorrhaging", + "haemorrhoids": "hemorrhoids", + "harbour": "harbor", + "harboured": "harbored", + "harbouring": "harboring", + "harbours": "harbors", + "harmonisation": "harmonization", + "harmonise": "harmonize", + "harmonised": "harmonized", + "harmonises": "harmonizes", + "harmonising": "harmonizing", + "homoeopath": "homeopath", + "homoeopathic": "homeopathic", + "homoeopaths": "homeopaths", + "homoeopathy": "homeopathy", + "homogenise": "homogenize", + "homogenised": "homogenized", + "homogenises": "homogenizes", + "homogenising": "homogenizing", + "honour": "honor", + "honourable": "honorable", + "honourably": "honorably", + "honoured": "honored", + "honouring": "honoring", + "honours": "honors", + "hospitalisation": "hospitalization", + "hospitalise": "hospitalize", + "hospitalised": "hospitalized", + "hospitalises": "hospitalizes", + "hospitalising": "hospitalizing", + "humanise": "humanize", + "humanised": "humanized", + "humanises": "humanizes", + "humanising": "humanizing", + "humour": "humor", + "humoured": "humored", + "humouring": "humoring", + "humourless": "humorless", + "humours": "humors", + "hybridise": "hybridize", + "hybridised": "hybridized", + "hybridises": "hybridizes", + "hybridising": "hybridizing", + "hypnotise": "hypnotize", + "hypnotised": "hypnotized", + "hypnotises": "hypnotizes", + "hypnotising": "hypnotizing", + "hypothesise": "hypothesize", + "hypothesised": "hypothesized", + "hypothesises": "hypothesizes", + "hypothesising": "hypothesizing", + "idealisation": "idealization", + "idealise": "idealize", + "idealised": "idealized", + "idealises": "idealizes", + "idealising": "idealizing", + "idolise": "idolize", + "idolised": "idolized", + "idolises": "idolizes", + "idolising": "idolizing", + "immobilisation": "immobilization", + "immobilise": "immobilize", + "immobilised": "immobilized", + "immobiliser": "immobilizer", + "immobilisers": "immobilizers", + "immobilises": "immobilizes", + "immobilising": "immobilizing", + "immortalise": "immortalize", + "immortalised": "immortalized", + "immortalises": "immortalizes", + "immortalising": "immortalizing", + "immunisation": "immunization", + "immunise": "immunize", + "immunised": "immunized", + "immunises": "immunizes", + "immunising": "immunizing", + "impanelled": "impaneled", + "impanelling": "impaneling", + "imperilled": "imperiled", + "imperilling": "imperiling", + "individualise": "individualize", + "individualised": "individualized", + "individualises": "individualizes", + "individualising": "individualizing", + "industrialise": "industrialize", + "industrialised": "industrialized", + "industrialises": "industrializes", + "industrialising": "industrializing", + "inflexion": "inflection", + "inflexions": "inflections", + "initialise": "initialize", + "initialised": "initialized", + "initialises": "initializes", + "initialising": "initializing", + "initialled": "initialed", + "initialling": "initialing", + "instal": "install", + "instalment": "installment", + "instalments": "installments", + "instals": "installs", + "instil": "instill", + "instils": "instills", + "institutionalisation": "institutionalization", + "institutionalise": "institutionalize", + "institutionalised": "institutionalized", + "institutionalises": "institutionalizes", + "institutionalising": "institutionalizing", + "intellectualise": "intellectualize", + "intellectualised": "intellectualized", + "intellectualises": "intellectualizes", + "intellectualising": "intellectualizing", + "internalisation": "internalization", + "internalise": "internalize", + "internalised": "internalized", + "internalises": "internalizes", + "internalising": "internalizing", + "internationalisation": "internationalization", + "internationalise": "internationalize", + "internationalised": "internationalized", + "internationalises": "internationalizes", + "internationalising": "internationalizing", + "ionisation": "ionization", + "ionise": "ionize", + "ionised": "ionized", + "ioniser": "ionizer", + "ionisers": "ionizers", + "ionises": "ionizes", + "ionising": "ionizing", + "italicise": "italicize", + "italicised": "italicized", + "italicises": "italicizes", + "italicising": "italicizing", + "itemise": "itemize", + "itemised": "itemized", + "itemises": "itemizes", + "itemising": "itemizing", + "jeopardise": "jeopardize", + "jeopardised": "jeopardized", + "jeopardises": "jeopardizes", + "jeopardising": "jeopardizing", + "jewelled": "jeweled", + "jeweller": "jeweler", + "jewellers": "jewelers", + "jewellery": "jewelry", + "judgement ": "judgment", + "kilogramme": "kilogram", + "kilogrammes": "kilograms", + "kilometre": "kilometer", + "kilometres": "kilometers", + "labelled": "labeled", + "labelling": "labeling", + "labour": "labor", + "laboured": "labored", + "labourer": "laborer", + "labourers": "laborers", + "labouring": "laboring", + "labours": "labors", + "lacklustre": "lackluster", + "legalisation": "legalization", + "legalise": "legalize", + "legalised": "legalized", + "legalises": "legalizes", + "legalising": "legalizing", + "legitimise": "legitimize", + "legitimised": "legitimized", + "legitimises": "legitimizes", + "legitimising": "legitimizing", + "leukaemia": "leukemia", + "levelled": "leveled", + "leveller": "leveler", + "levellers": "levelers", + "levelling": "leveling", + "libelled": "libeled", + "libelling": "libeling", + "libellous": "libelous", + "liberalisation": "liberalization", + "liberalise": "liberalize", + "liberalised": "liberalized", + "liberalises": "liberalizes", + "liberalising": "liberalizing", + "licence": "license", + "licenced": "licensed", + "licences": "licenses", + "licencing": "licensing", + "likeable": "likable ", + "lionisation": "lionization", + "lionise": "lionize", + "lionised": "lionized", + "lionises": "lionizes", + "lionising": "lionizing", + "liquidise": "liquidize", + "liquidised": "liquidized", + "liquidiser": "liquidizer", + "liquidisers": "liquidizers", + "liquidises": "liquidizes", + "liquidising": "liquidizing", + "litre": "liter", + "litres": "liters", + "localise": "localize", + "localised": "localized", + "localises": "localizes", + "localising": "localizing", + "louvre": "louver", + "louvred": "louvered", + "louvres": "louvers ", + "lustre": "luster", + "magnetise": "magnetize", + "magnetised": "magnetized", + "magnetises": "magnetizes", + "magnetising": "magnetizing", + "manoeuvrability": "maneuverability", + "manoeuvrable": "maneuverable", + "manoeuvre": "maneuver", + "manoeuvred": "maneuvered", + "manoeuvres": "maneuvers", + "manoeuvring": "maneuvering", + "manoeuvrings": "maneuverings", + "marginalisation": "marginalization", + "marginalise": "marginalize", + "marginalised": "marginalized", + "marginalises": "marginalizes", + "marginalising": "marginalizing", + "marshalled": "marshaled", + "marshalling": "marshaling", + "marvelled": "marveled", + "marvelling": "marveling", + "marvellous": "marvelous", + "marvellously": "marvelously", + "materialisation": "materialization", + "materialise": "materialize", + "materialised": "materialized", + "materialises": "materializes", + "materialising": "materializing", + "maximisation": "maximization", + "maximise": "maximize", + "maximised": "maximized", + "maximises": "maximizes", + "maximising": "maximizing", + "meagre": "meager", + "mechanisation": "mechanization", + "mechanise": "mechanize", + "mechanised": "mechanized", + "mechanises": "mechanizes", + "mechanising": "mechanizing", + "mediaeval": "medieval", + "memorialise": "memorialize", + "memorialised": "memorialized", + "memorialises": "memorializes", + "memorialising": "memorializing", + "memorise": "memorize", + "memorised": "memorized", + "memorises": "memorizes", + "memorising": "memorizing", + "mesmerise": "mesmerize", + "mesmerised": "mesmerized", + "mesmerises": "mesmerizes", + "mesmerising": "mesmerizing", + "metabolise": "metabolize", + "metabolised": "metabolized", + "metabolises": "metabolizes", + "metabolising": "metabolizing", + "metre": "meter", + "metres": "meters", + "micrometre": "micrometer", + "micrometres": "micrometers", + "militarise": "militarize", + "militarised": "militarized", + "militarises": "militarizes", + "militarising": "militarizing", + "milligramme": "milligram", + "milligrammes": "milligrams", + "millilitre": "milliliter", + "millilitres": "milliliters", + "millimetre": "millimeter", + "millimetres": "millimeters", + "miniaturisation": "miniaturization", + "miniaturise": "miniaturize", + "miniaturised": "miniaturized", + "miniaturises": "miniaturizes", + "miniaturising": "miniaturizing", + "minibuses": "minibusses ", + "minimise": "minimize", + "minimised": "minimized", + "minimises": "minimizes", + "minimising": "minimizing", + "misbehaviour": "misbehavior", + "misdemeanour": "misdemeanor", + "misdemeanours": "misdemeanors", + "misspelt": "misspelled ", + "mitre": "miter", + "mitres": "miters", + "mobilisation": "mobilization", + "mobilise": "mobilize", + "mobilised": "mobilized", + "mobilises": "mobilizes", + "mobilising": "mobilizing", + "modelled": "modeled", + "modeller": "modeler", + "modellers": "modelers", + "modelling": "modeling", + "modernise": "modernize", + "modernised": "modernized", + "modernises": "modernizes", + "modernising": "modernizing", + "moisturise": "moisturize", + "moisturised": "moisturized", + "moisturiser": "moisturizer", + "moisturisers": "moisturizers", + "moisturises": "moisturizes", + "moisturising": "moisturizing", + "monologue": "monolog", + "monologues": "monologs", + "monopolisation": "monopolization", + "monopolise": "monopolize", + "monopolised": "monopolized", + "monopolises": "monopolizes", + "monopolising": "monopolizing", + "moralise": "moralize", + "moralised": "moralized", + "moralises": "moralizes", + "moralising": "moralizing", + "motorised": "motorized", + "mould": "mold", + "moulded": "molded", + "moulder": "molder", + "mouldered": "moldered", + "mouldering": "moldering", + "moulders": "molders", + "mouldier": "moldier", + "mouldiest": "moldiest", + "moulding": "molding", + "mouldings": "moldings", + "moulds": "molds", + "mouldy": "moldy", + "moult": "molt", + "moulted": "molted", + "moulting": "molting", + "moults": "molts", + "moustache": "mustache", + "moustached": "mustached", + "moustaches": "mustaches", + "moustachioed": "mustachioed", + "multicoloured": "multicolored", + "nationalisation": "nationalization", + "nationalisations": "nationalizations", + "nationalise": "nationalize", + "nationalised": "nationalized", + "nationalises": "nationalizes", + "nationalising": "nationalizing", + "naturalisation": "naturalization", + "naturalise": "naturalize", + "naturalised": "naturalized", + "naturalises": "naturalizes", + "naturalising": "naturalizing", + "neighbour": "neighbor", + "neighbourhood": "neighborhood", + "neighbourhoods": "neighborhoods", + "neighbouring": "neighboring", + "neighbourliness": "neighborliness", + "neighbourly": "neighborly", + "neighbours": "neighbors", + "neutralisation": "neutralization", + "neutralise": "neutralize", + "neutralised": "neutralized", + "neutralises": "neutralizes", + "neutralising": "neutralizing", + "normalisation": "normalization", + "normalise": "normalize", + "normalised": "normalized", + "normalises": "normalizes", + "normalising": "normalizing", + "odour": "odor", + "odourless": "odorless", + "odours": "odors", + "oesophagus": "esophagus", + "oesophaguses": "esophaguses", + "oestrogen": "estrogen", + "offence": "offense", + "offences": "offenses", + "omelette": "omelet", + "omelettes": "omelets", + "optimise": "optimize", + "optimised": "optimized", + "optimises": "optimizes", + "optimising": "optimizing", + "organisation": "organization", + "organisational": "organizational", + "organisations": "organizations", + "organise": "organize", + "organised": "organized", + "organiser": "organizer", + "organisers": "organizers", + "organises": "organizes", + "organising": "organizing", + "orthopaedic": "orthopedic", + "orthopaedics": "orthopedics", + "ostracise": "ostracize", + "ostracised": "ostracized", + "ostracises": "ostracizes", + "ostracising": "ostracizing", + "outmanoeuvre": "outmaneuver", + "outmanoeuvred": "outmaneuvered", + "outmanoeuvres": "outmaneuvers", + "outmanoeuvring": "outmaneuvering", + "overemphasise": "overemphasize", + "overemphasised": "overemphasized", + "overemphasises": "overemphasizes", + "overemphasising": "overemphasizing", + "oxidisation": "oxidization", + "oxidise": "oxidize", + "oxidised": "oxidized", + "oxidises": "oxidizes", + "oxidising": "oxidizing", + "paederast": "pederast", + "paederasts": "pederasts", + "paediatric": "pediatric", + "paediatrician": "pediatrician", + "paediatricians": "pediatricians", + "paediatrics": "pediatrics", + "paedophile": "pedophile", + "paedophiles": "pedophiles", + "paedophilia": "pedophilia", + "palaeolithic": "paleolithic", + "palaeontologist": "paleontologist", + "palaeontologists": "paleontologists", + "palaeontology": "paleontology", + "panelled": "paneled", + "panelling": "paneling", + "panellist": "panelist", + "panellists": "panelists", + "paralyse": "paralyze", + "paralysed": "paralyzed", + "paralyses": "paralyzes", + "paralysing": "paralyzing", + "parcelled": "parceled", + "parcelling": "parceling", + "parlour": "parlor", + "parlours": "parlors", + "particularise": "particularize", + "particularised": "particularized", + "particularises": "particularizes", + "particularising": "particularizing", + "passivisation": "passivization", + "passivise": "passivize", + "passivised": "passivized", + "passivises": "passivizes", + "passivising": "passivizing", + "pasteurisation": "pasteurization", + "pasteurise": "pasteurize", + "pasteurised": "pasteurized", + "pasteurises": "pasteurizes", + "pasteurising": "pasteurizing", + "patronise": "patronize", + "patronised": "patronized", + "patronises": "patronizes", + "patronising": "patronizing", + "patronisingly": "patronizingly", + "pedalled": "pedaled", + "pedalling": "pedaling", + "pedestrianisation": "pedestrianization", + "pedestrianise": "pedestrianize", + "pedestrianised": "pedestrianized", + "pedestrianises": "pedestrianizes", + "pedestrianising": "pedestrianizing", + "penalise": "penalize", + "penalised": "penalized", + "penalises": "penalizes", + "penalising": "penalizing", + "pencilled": "penciled", + "pencilling": "penciling", + "personalise": "personalize", + "personalised": "personalized", + "personalises": "personalizes", + "personalising": "personalizing", + "pharmacopoeia": "pharmacopeia", + "pharmacopoeias": "pharmacopeias", + "philosophise": "philosophize", + "philosophised": "philosophized", + "philosophises": "philosophizes", + "philosophising": "philosophizing", + "philtre": "filter", + "philtres": "filters", + "phoney ": "phony ", + "plagiarise": "plagiarize", + "plagiarised": "plagiarized", + "plagiarises": "plagiarizes", + "plagiarising": "plagiarizing", + "plough": "plow", + "ploughed": "plowed", + "ploughing": "plowing", + "ploughman": "plowman", + "ploughmen": "plowmen", + "ploughs": "plows", + "ploughshare": "plowshare", + "ploughshares": "plowshares", + "polarisation": "polarization", + "polarise": "polarize", + "polarised": "polarized", + "polarises": "polarizes", + "polarising": "polarizing", + "politicisation": "politicization", + "politicise": "politicize", + "politicised": "politicized", + "politicises": "politicizes", + "politicising": "politicizing", + "popularisation": "popularization", + "popularise": "popularize", + "popularised": "popularized", + "popularises": "popularizes", + "popularising": "popularizing", + "pouffe": "pouf", + "pouffes": "poufs", + "practise": "practice", + "practised": "practiced", + "practises": "practices", + "practising ": "practicing ", + "praesidium": "presidium", + "praesidiums ": "presidiums ", + "pressurisation": "pressurization", + "pressurise": "pressurize", + "pressurised": "pressurized", + "pressurises": "pressurizes", + "pressurising": "pressurizing", + "pretence": "pretense", + "pretences": "pretenses", + "primaeval": "primeval", + "prioritisation": "prioritization", + "prioritise": "prioritize", + "prioritised": "prioritized", + "prioritises": "prioritizes", + "prioritising": "prioritizing", + "privatisation": "privatization", + "privatisations": "privatizations", + "privatise": "privatize", + "privatised": "privatized", + "privatises": "privatizes", + "privatising": "privatizing", + "professionalisation": "professionalization", + "professionalise": "professionalize", + "professionalised": "professionalized", + "professionalises": "professionalizes", + "professionalising": "professionalizing", + "programme": "program", + "programmes": "programs", + "prologue": "prolog", + "prologues": "prologs", + "propagandise": "propagandize", + "propagandised": "propagandized", + "propagandises": "propagandizes", + "propagandising": "propagandizing", + "proselytise": "proselytize", + "proselytised": "proselytized", + "proselytiser": "proselytizer", + "proselytisers": "proselytizers", + "proselytises": "proselytizes", + "proselytising": "proselytizing", + "psychoanalyse": "psychoanalyze", + "psychoanalysed": "psychoanalyzed", + "psychoanalyses": "psychoanalyzes", + "psychoanalysing": "psychoanalyzing", + "publicise": "publicize", + "publicised": "publicized", + "publicises": "publicizes", + "publicising": "publicizing", + "pulverisation": "pulverization", + "pulverise": "pulverize", + "pulverised": "pulverized", + "pulverises": "pulverizes", + "pulverising": "pulverizing", + "pummelled": "pummel", + "pummelling": "pummeled", + "pyjama": "pajama", + "pyjamas": "pajamas", + "pzazz": "pizzazz", + "quarrelled": "quarreled", + "quarrelling": "quarreling", + "radicalise": "radicalize", + "radicalised": "radicalized", + "radicalises": "radicalizes", + "radicalising": "radicalizing", + "rancour": "rancor", + "randomise": "randomize", + "randomised": "randomized", + "randomises": "randomizes", + "randomising": "randomizing", + "rationalisation": "rationalization", + "rationalisations": "rationalizations", + "rationalise": "rationalize", + "rationalised": "rationalized", + "rationalises": "rationalizes", + "rationalising": "rationalizing", + "ravelled": "raveled", + "ravelling": "raveling", + "realisable": "realizable", + "realisation": "realization", + "realisations": "realizations", + "realise": "realize", + "realised": "realized", + "realises": "realizes", + "realising": "realizing", + "recognisable": "recognizable", + "recognisably": "recognizably", + "recognisance": "recognizance", + "recognise": "recognize", + "recognised": "recognized", + "recognises": "recognizes", + "recognising": "recognizing", + "reconnoitre": "reconnoiter", + "reconnoitred": "reconnoitered", + "reconnoitres": "reconnoiters", + "reconnoitring": "reconnoitering", + "refuelled": "refueled", + "refuelling": "refueling", + "regularisation": "regularization", + "regularise": "regularize", + "regularised": "regularized", + "regularises": "regularizes", + "regularising": "regularizing", + "remodelled": "remodeled", + "remodelling": "remodeling", + "remould": "remold", + "remoulded": "remolded", + "remoulding": "remolding", + "remoulds": "remolds", + "reorganisation": "reorganization", + "reorganisations": "reorganizations", + "reorganise": "reorganize", + "reorganised": "reorganized", + "reorganises": "reorganizes", + "reorganising": "reorganizing", + "revelled": "reveled", + "reveller": "reveler", + "revellers": "revelers", + "revelling": "reveling", + "revitalise": "revitalize", + "revitalised": "revitalized", + "revitalises": "revitalizes", + "revitalising": "revitalizing", + "revolutionise": "revolutionize", + "revolutionised": "revolutionized", + "revolutionises": "revolutionizes", + "revolutionising": "revolutionizing", + "rhapsodise": "rhapsodize", + "rhapsodised": "rhapsodized", + "rhapsodises": "rhapsodizes", + "rhapsodising": "rhapsodizing", + "rigour": "rigor", + "rigours": "rigors", + "ritualised": "ritualized", + "rivalled": "rivaled", + "rivalling": "rivaling", + "romanticise": "romanticize", + "romanticised": "romanticized", + "romanticises": "romanticizes", + "romanticising": "romanticizing", + "rumour": "rumor", + "rumoured": "rumored", + "rumours": "rumors", + "sabre": "saber", + "sabres": "sabers", + "saltpetre": "saltpeter", + "sanitise": "sanitize", + "sanitised": "sanitized", + "sanitises": "sanitizes", + "sanitising": "sanitizing", + "satirise": "satirize", + "satirised": "satirized", + "satirises": "satirizes", + "satirising": "satirizing", + "saviour": "savior", + "saviours": "saviors", + "savour": "savor", + "savoured": "savored", + "savouries": "savories", + "savouring": "savoring", + "savours": "savors", + "savoury": "savory", + "scandalise": "scandalize", + "scandalised": "scandalized", + "scandalises": "scandalizes", + "scandalising": "scandalizing", + "sceptic": "skeptic", + "sceptical": "skeptical", + "sceptically": "skeptically", + "scepticism": "skepticism", + "sceptics": "skeptics", + "sceptre": "scepter", + "sceptres": "scepters", + "scrutinise": "scrutinize", + "scrutinised": "scrutinized", + "scrutinises": "scrutinizes", + "scrutinising": "scrutinizing", + "secularisation": "secularization", + "secularise": "secularize", + "secularised": "secularized", + "secularises": "secularizes", + "secularising": "secularizing", + "sensationalise": "sensationalize", + "sensationalised": "sensationalized", + "sensationalises": "sensationalizes", + "sensationalising": "sensationalizing", + "sensitise": "sensitize", + "sensitised": "sensitized", + "sensitises": "sensitizes", + "sensitising": "sensitizing", + "sentimentalise": "sentimentalize", + "sentimentalised": "sentimentalized", + "sentimentalises": "sentimentalizes", + "sentimentalising": "sentimentalizing", + "sepulchre": "sepulcher", + "sepulchres": "sepulchers ", + "serialisation": "serialization", + "serialisations": "serializations", + "serialise": "serialize", + "serialised": "serialized", + "serialises": "serializes", + "serialising": "serializing", + "sermonise": "sermonize", + "sermonised": "sermonized", + "sermonises": "sermonizes", + "sermonising": "sermonizing", + "sheikh ": "sheik ", + "shovelled": "shoveled", + "shovelling": "shoveling", + "shrivelled": "shriveled", + "shrivelling": "shriveling", + "signalise": "signalize", + "signalised": "signalized", + "signalises": "signalizes", + "signalising": "signalizing", + "signalled": "signaled", + "signalling": "signaling", + "smoulder": "smolder", + "smouldered": "smoldered", + "smouldering": "smoldering", + "smoulders": "smolders", + "snivelled": "sniveled", + "snivelling": "sniveling", + "snorkelled": "snorkeled", + "snorkelling": "snorkeling", + "snowplough": "snowplow", + "snowploughs": "snowplow", + "socialisation": "socialization", + "socialise": "socialize", + "socialised": "socialized", + "socialises": "socializes", + "socialising": "socializing", + "sodomise": "sodomize", + "sodomised": "sodomized", + "sodomises": "sodomizes", + "sodomising": "sodomizing", + "solemnise": "solemnize", + "solemnised": "solemnized", + "solemnises": "solemnizes", + "solemnising": "solemnizing", + "sombre": "somber", + "specialisation": "specialization", + "specialisations": "specializations", + "specialise": "specialize", + "specialised": "specialized", + "specialises": "specializes", + "specialising": "specializing", + "spectre": "specter", + "spectres": "specters", + "spiralled": "spiraled", + "spiralling": "spiraling", + "splendour": "splendor", + "splendours": "splendors", + "squirrelled": "squirreled", + "squirrelling": "squirreling", + "stabilisation": "stabilization", + "stabilise": "stabilize", + "stabilised": "stabilized", + "stabiliser": "stabilizer", + "stabilisers": "stabilizers", + "stabilises": "stabilizes", + "stabilising": "stabilizing", + "standardisation": "standardization", + "standardise": "standardize", + "standardised": "standardized", + "standardises": "standardizes", + "standardising": "standardizing", + "stencilled": "stenciled", + "stencilling": "stenciling", + "sterilisation": "sterilization", + "sterilisations": "sterilizations", + "sterilise": "sterilize", + "sterilised": "sterilized", + "steriliser": "sterilizer", + "sterilisers": "sterilizers", + "sterilises": "sterilizes", + "sterilising": "sterilizing", + "stigmatisation": "stigmatization", + "stigmatise": "stigmatize", + "stigmatised": "stigmatized", + "stigmatises": "stigmatizes", + "stigmatising": "stigmatizing", + "storey": "story", + "storeys": "stories", + "subsidisation": "subsidization", + "subsidise": "subsidize", + "subsidised": "subsidized", + "subsidiser": "subsidizer", + "subsidisers": "subsidizers", + "subsidises": "subsidizes", + "subsidising": "subsidizing", + "succour": "succor", + "succoured": "succored", + "succouring": "succoring", + "succours": "succors", + "sulphate": "sulfate", + "sulphates": "sulfates", + "sulphide": "sulfide", + "sulphides": "sulfides", + "sulphur": "sulfur", + "sulphurous": "sulfurous", + "summarise": "summarize", + "summarised": "summarized", + "summarises": "summarizes", + "summarising": "summarizing", + "swivelled": "swiveled", + "swivelling": "swiveling", + "symbolise": "symbolize", + "symbolised": "symbolized", + "symbolises": "symbolizes", + "symbolising": "symbolizing", + "sympathise": "sympathize", + "sympathised": "sympathized", + "sympathiser": "sympathizer", + "sympathisers": "sympathizers", + "sympathises": "sympathizes", + "sympathising": "sympathizing", + "synchronisation": "synchronization", + "synchronise": "synchronize", + "synchronised": "synchronized", + "synchronises": "synchronizes", + "synchronising": "synchronizing", + "synthesise": "synthesize", + "synthesised": "synthesized", + "synthesiser": "synthesizer", + "synthesisers": "synthesizers", + "synthesises": "synthesizes", + "synthesising": "synthesizing", + "syphon": "siphon", + "syphoned": "siphoned", + "syphoning": "siphoning", + "syphons": "siphons", + "systematisation": "systematization", + "systematise": "systematize", + "systematised": "systematized", + "systematises": "systematizes", + "systematising": "systematizing", + "tantalise": "tantalize", + "tantalised": "tantalized", + "tantalises": "tantalizes", + "tantalising": "tantalizing", + "tantalisingly": "tantalizingly", + "tasselled": "tasseled", + "technicolour": "technicolor", + "temporise": "temporize", + "temporised": "temporized", + "temporises": "temporizes", + "temporising": "temporizing", + "tenderise": "tenderize", + "tenderised": "tenderized", + "tenderises": "tenderizes", + "tenderising": "tenderizing", + "terrorise": "terrorize", + "terrorised": "terrorized", + "terrorises": "terrorizes", + "terrorising": "terrorizing", + "theatre": "theater", + "theatregoer": "theatergoer", + "theatregoers": "theatergoers", + "theatres": "theaters", + "theorise": "theorize", + "theorised": "theorized", + "theorises": "theorizes", + "theorising": "theorizing", + "tonne": "ton", + "tonnes": "tons", + "towelled": "toweled", + "towelling": "toweling", + "toxaemia": "toxemia", + "tranquillise": "tranquilize", + "tranquillised": "tranquilized", + "tranquilliser": "tranquilizer", + "tranquillisers": "tranquilizers", + "tranquillises": "tranquilizes", + "tranquillising": "tranquilizing", + "tranquillity": "tranquility", + "tranquillize": "tranquilize", + "tranquillized": "tranquilized", + "tranquillizer": "tranquilizer", + "tranquillizers": "tranquilizers", + "tranquillizes": "tranquilizes", + "tranquillizing": "tranquilizing", + "tranquilly": "tranquility", + "transistorised": "transistorized", + "traumatise": "traumatize", + "traumatised": "traumatized", + "traumatises": "traumatizes", + "traumatising": "traumatizing", + "travelled": "traveled", + "traveller": "traveler", + "travellers": "travelers", + "travelling": "traveling", + "travelogue": "travelog", + "travelogues ": "travelogs ", + "trialled": "trialed", + "trialling": "trialing", + "tricolour": "tricolor", + "tricolours": "tricolors", + "trivialise": "trivialize", + "trivialised": "trivialized", + "trivialises": "trivializes", + "trivialising": "trivializing", + "tumour": "tumor", + "tumours": "tumors", + "tunnelled": "tunneled", + "tunnelling": "tunneling", + "tyrannise": "tyrannize", + "tyrannised": "tyrannized", + "tyrannises": "tyrannizes", + "tyrannising": "tyrannizing", + "tyre": "tire", + "tyres": "tires", + "unauthorised": "unauthorized", + "uncivilised": "uncivilized", + "underutilised": "underutilized", + "unequalled": "unequaled", + "unfavourable": "unfavorable", + "unfavourably": "unfavorably", + "unionisation": "unionization", + "unionise": "unionize", + "unionised": "unionized", + "unionises": "unionizes", + "unionising": "unionizing", + "unorganised": "unorganized", + "unravelled": "unraveled", + "unravelling": "unraveling", + "unrecognisable": "unrecognizable", + "unrecognised": "unrecognized", + "unrivalled": "unrivaled", + "unsavoury": "unsavory", + "untrammelled": "untrammeled", + "urbanisation": "urbanization", + "urbanise": "urbanize", + "urbanised": "urbanized", + "urbanises": "urbanizes", + "urbanising": "urbanizing", + "utilisable": "utilizable", + "utilisation": "utilization", + "utilise": "utilize", + "utilised": "utilized", + "utilises": "utilizes", + "utilising": "utilizing", + "valour": "valor", + "vandalise": "vandalize", + "vandalised": "vandalized", + "vandalises": "vandalizes", + "vandalising": "vandalizing", + "vaporisation": "vaporization", + "vaporise": "vaporize", + "vaporised": "vaporized", + "vaporises": "vaporizes", + "vaporising": "vaporizing", + "vapour": "vapor", + "vapours": "vapors", + "verbalise": "verbalize", + "verbalised": "verbalized", + "verbalises": "verbalizes", + "verbalising": "verbalizing", + "victimisation": "victimization", + "victimise": "victimize", + "victimised": "victimized", + "victimises": "victimizes", + "victimising": "victimizing", + "videodisc": "videodisk", + "videodiscs": "videodisks", + "vigour": "vigor", + "visualisation": "visualization", + "visualisations": "visualizations", + "visualise": "visualize", + "visualised": "visualized", + "visualises": "visualizes", + "visualising": "visualizing", + "vocalisation": "vocalization", + "vocalisations": "vocalizations", + "vocalise": "vocalize", + "vocalised": "vocalized", + "vocalises": "vocalizes", + "vocalising": "vocalizing", + "vulcanised": "vulcanized", + "vulgarisation": "vulgarization", + "vulgarise": "vulgarize", + "vulgarised": "vulgarized", + "vulgarises": "vulgarizes", + "vulgarising": "vulgarizing", + "waggon": "wagon", + "waggons": "wagons", + "watercolour": "watercolor", + "watercolours": "watercolors", + "weaselled": "weaseled", + "weaselling": "weaseling", + "westernisation": "westernization", + "westernise": "westernize", + "westernised": "westernized", + "westernises": "westernizes", + "westernising": "westernizing", + "womanise": "womanize", + "womanised": "womanized", + "womaniser": "womanizer", + "womanisers": "womanizers", + "womanises": "womanizes", + "womanising": "womanizing", + "woollen": "woolen", + "woollens": "woolens", + "woollies": "woolies", + "woolly": "wooly", + "worshipped ": "worshiped", + "worshipping ": "worshiping ", + "worshipper": "worshiper", + "yodelled": "yodeled", + "yodelling": "yodeling", + "yoghourt": "yogurt", + "yoghourts": "yogurts", + "yoghurt": "yogurt", + "yoghurts": "yogurts" +} + + +NORM_EXCEPTIONS = {} + +for string, norm in _exc.items(): + NORM_EXCEPTIONS[string] = norm + NORM_EXCEPTIONS[string.title()] = norm diff --git a/spacy/lang/en/syntax_iterators.py b/spacy/lang/en/syntax_iterators.py index dec240669..4240bd657 100644 --- a/spacy/lang/en/syntax_iterators.py +++ b/spacy/lang/en/syntax_iterators.py @@ -11,9 +11,9 @@ def noun_chunks(obj): labels = ['nsubj', 'dobj', 'nsubjpass', 'pcomp', 'pobj', 'attr', 'ROOT'] doc = obj.doc # Ensure works on both Doc and Span. - np_deps = [doc.vocab.strings[label] for label in labels] - conj = doc.vocab.strings['conj'] - np_label = doc.vocab.strings['NP'] + np_deps = [doc.vocab.strings.add(label) for label in labels] + conj = doc.vocab.strings.add('conj') + np_label = doc.vocab.strings.add('NP') seen = set() for i, word in enumerate(obj): if word.pos not in (NOUN, PROPN, PRON): diff --git a/spacy/lang/en/tokenizer_exceptions.py b/spacy/lang/en/tokenizer_exceptions.py index 5c6e3f893..392532619 100644 --- a/spacy/lang/en/tokenizer_exceptions.py +++ b/spacy/lang/en/tokenizer_exceptions.py @@ -15,20 +15,20 @@ _exclude = ["Ill", "ill", "Its", "its", "Hell", "hell", "Shell", "shell", for pron in ["i"]: for orth in [pron, pron.title()]: _exc[orth + "'m"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, - {ORTH: "'m", LEMMA: "be", TAG: "VBP", "tenspect": 1, "number": 1}] + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, + {ORTH: "'m", LEMMA: "be", NORM: "am", TAG: "VBP", "tenspect": 1, "number": 1}] _exc[orth + "m"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, {ORTH: "m", LEMMA: "be", TAG: "VBP", "tenspect": 1, "number": 1 }] _exc[orth + "'ma"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, {ORTH: "'m", LEMMA: "be", NORM: "am"}, {ORTH: "a", LEMMA: "going to", NORM: "gonna"}] _exc[orth + "ma"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, {ORTH: "m", LEMMA: "be", NORM: "am"}, {ORTH: "a", LEMMA: "going to", NORM: "gonna"}] @@ -36,72 +36,72 @@ for pron in ["i"]: for pron in ["i", "you", "he", "she", "it", "we", "they"]: for orth in [pron, pron.title()]: _exc[orth + "'ll"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, - {ORTH: "'ll", LEMMA: "will", TAG: "MD"}] + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, + {ORTH: "'ll", LEMMA: "will", NORM: "will", TAG: "MD"}] _exc[orth + "ll"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, - {ORTH: "ll", LEMMA: "will", TAG: "MD"}] + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, + {ORTH: "ll", LEMMA: "will", NORM: "will", TAG: "MD"}] _exc[orth + "'ll've"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, - {ORTH: "'ll", LEMMA: "will", TAG: "MD"}, - {ORTH: "'ve", LEMMA: "have", TAG: "VB"}] + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, + {ORTH: "'ll", LEMMA: "will", NORM: "will", TAG: "MD"}, + {ORTH: "'ve", LEMMA: "have", NORM: "have", TAG: "VB"}] _exc[orth + "llve"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, - {ORTH: "ll", LEMMA: "will", TAG: "MD"}, - {ORTH: "ve", LEMMA: "have", TAG: "VB"}] + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, + {ORTH: "ll", LEMMA: "will", NORM: "will", TAG: "MD"}, + {ORTH: "ve", LEMMA: "have", NORM: "have", TAG: "VB"}] _exc[orth + "'d"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, - {ORTH: "'d", LEMMA: "would", TAG: "MD"}] + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, + {ORTH: "'d", LEMMA: "would", NORM: "would", TAG: "MD"}] _exc[orth + "d"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, - {ORTH: "d", LEMMA: "would", TAG: "MD"}] + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, + {ORTH: "d", LEMMA: "would", NORM: "would", TAG: "MD"}] _exc[orth + "'d've"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, - {ORTH: "'d", LEMMA: "would", TAG: "MD"}, - {ORTH: "'ve", LEMMA: "have", TAG: "VB"}] + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, + {ORTH: "'d", LEMMA: "would", NORM: "would", TAG: "MD"}, + {ORTH: "'ve", LEMMA: "have", NORM: "have", TAG: "VB"}] _exc[orth + "dve"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, - {ORTH: "d", LEMMA: "would", TAG: "MD"}, - {ORTH: "ve", LEMMA: "have", TAG: "VB"}] + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, + {ORTH: "d", LEMMA: "would", NORM: "would", TAG: "MD"}, + {ORTH: "ve", LEMMA: "have", NORM: "have", TAG: "VB"}] for pron in ["i", "you", "we", "they"]: for orth in [pron, pron.title()]: _exc[orth + "'ve"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, - {ORTH: "'ve", LEMMA: "have", TAG: "VB"}] + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, + {ORTH: "'ve", LEMMA: "have", NORM: "have", TAG: "VB"}] _exc[orth + "ve"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, - {ORTH: "ve", LEMMA: "have", TAG: "VB"}] + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, + {ORTH: "ve", LEMMA: "have", NORM: "have", TAG: "VB"}] for pron in ["you", "we", "they"]: for orth in [pron, pron.title()]: _exc[orth + "'re"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, {ORTH: "'re", LEMMA: "be", NORM: "are"}] _exc[orth + "re"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, {ORTH: "re", LEMMA: "be", NORM: "are", TAG: "VBZ"}] for pron in ["he", "she", "it"]: for orth in [pron, pron.title()]: _exc[orth + "'s"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, - {ORTH: "'s"}] + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, + {ORTH: "'s", NORM: "'s"}] _exc[orth + "s"] = [ - {ORTH: orth, LEMMA: PRON_LEMMA, TAG: "PRP"}, + {ORTH: orth, LEMMA: PRON_LEMMA, NORM: pron, TAG: "PRP"}, {ORTH: "s"}] @@ -110,111 +110,111 @@ for pron in ["he", "she", "it"]: for word in ["who", "what", "when", "where", "why", "how", "there", "that"]: for orth in [word, word.title()]: _exc[orth + "'s"] = [ - {ORTH: orth, LEMMA: word}, - {ORTH: "'s"}] + {ORTH: orth, LEMMA: word, NORM: word}, + {ORTH: "'s", NORM: "'s"}] _exc[orth + "s"] = [ - {ORTH: orth, LEMMA: word}, + {ORTH: orth, LEMMA: word, NORM: word}, {ORTH: "s"}] _exc[orth + "'ll"] = [ - {ORTH: orth, LEMMA: word}, - {ORTH: "'ll", LEMMA: "will", TAG: "MD"}] + {ORTH: orth, LEMMA: word, NORM: word}, + {ORTH: "'ll", LEMMA: "will", NORM: "will", TAG: "MD"}] _exc[orth + "ll"] = [ - {ORTH: orth, LEMMA: word}, - {ORTH: "ll", LEMMA: "will", TAG: "MD"}] + {ORTH: orth, LEMMA: word, NORM: word}, + {ORTH: "ll", LEMMA: "will", NORM: "will", TAG: "MD"}] _exc[orth + "'ll've"] = [ - {ORTH: orth, LEMMA: word}, - {ORTH: "'ll", LEMMA: "will", TAG: "MD"}, - {ORTH: "'ve", LEMMA: "have", TAG: "VB"}] + {ORTH: orth, LEMMA: word, NORM: word}, + {ORTH: "'ll", LEMMA: "will", NORM: "will", TAG: "MD"}, + {ORTH: "'ve", LEMMA: "have", NORM: "have", TAG: "VB"}] _exc[orth + "llve"] = [ - {ORTH: orth, LEMMA: word}, - {ORTH: "ll", LEMMA: "will", TAG: "MD"}, - {ORTH: "ve", LEMMA: "have", TAG: "VB"}] + {ORTH: orth, LEMMA: word, NORM: word}, + {ORTH: "ll", LEMMA: "will", NORM: "will", TAG: "MD"}, + {ORTH: "ve", LEMMA: "have", NORM: "have", TAG: "VB"}] _exc[orth + "'re"] = [ - {ORTH: orth, LEMMA: word}, + {ORTH: orth, LEMMA: word, NORM: word}, {ORTH: "'re", LEMMA: "be", NORM: "are"}] _exc[orth + "re"] = [ - {ORTH: orth, LEMMA: word}, + {ORTH: orth, LEMMA: word, NORM: word}, {ORTH: "re", LEMMA: "be", NORM: "are"}] _exc[orth + "'ve"] = [ - {ORTH: orth}, + {ORTH: orth, LEMMA: word, NORM: word}, {ORTH: "'ve", LEMMA: "have", TAG: "VB"}] _exc[orth + "ve"] = [ {ORTH: orth, LEMMA: word}, - {ORTH: "ve", LEMMA: "have", TAG: "VB"}] + {ORTH: "ve", LEMMA: "have", NORM: "have", TAG: "VB"}] _exc[orth + "'d"] = [ - {ORTH: orth, LEMMA: word}, - {ORTH: "'d"}] + {ORTH: orth, LEMMA: word, NORM: word}, + {ORTH: "'d", NORM: "'d"}] _exc[orth + "d"] = [ - {ORTH: orth, LEMMA: word}, + {ORTH: orth, LEMMA: word, NORM: word}, {ORTH: "d"}] _exc[orth + "'d've"] = [ - {ORTH: orth, LEMMA: word}, - {ORTH: "'d", LEMMA: "would", TAG: "MD"}, - {ORTH: "'ve", LEMMA: "have", TAG: "VB"}] + {ORTH: orth, LEMMA: word, NORM: word}, + {ORTH: "'d", LEMMA: "would", NORM: "would", TAG: "MD"}, + {ORTH: "'ve", LEMMA: "have", NORM: "have", TAG: "VB"}] _exc[orth + "dve"] = [ - {ORTH: orth, LEMMA: word}, - {ORTH: "d", LEMMA: "would", TAG: "MD"}, - {ORTH: "ve", LEMMA: "have", TAG: "VB"}] + {ORTH: orth, LEMMA: word, NORM: word}, + {ORTH: "d", LEMMA: "would", NORM: "would", TAG: "MD"}, + {ORTH: "ve", LEMMA: "have", NORM: "have", TAG: "VB"}] # Verbs for verb_data in [ - {ORTH: "ca", LEMMA: "can", TAG: "MD"}, - {ORTH: "could", TAG: "MD"}, - {ORTH: "do", LEMMA: "do"}, - {ORTH: "does", LEMMA: "do"}, - {ORTH: "did", LEMMA: "do", TAG: "VBD"}, - {ORTH: "had", LEMMA: "have", TAG: "VBD"}, - {ORTH: "may", TAG: "MD"}, - {ORTH: "might", TAG: "MD"}, - {ORTH: "must", TAG: "MD"}, - {ORTH: "need"}, - {ORTH: "ought"}, - {ORTH: "sha", LEMMA: "shall", TAG: "MD"}, - {ORTH: "should", TAG: "MD"}, - {ORTH: "wo", LEMMA: "will", TAG: "MD"}, - {ORTH: "would", TAG: "MD"}]: + {ORTH: "ca", LEMMA: "can", NORM: "can", TAG: "MD"}, + {ORTH: "could", NORM: "could", TAG: "MD"}, + {ORTH: "do", LEMMA: "do", NORM: "do"}, + {ORTH: "does", LEMMA: "do", NORM: "does"}, + {ORTH: "did", LEMMA: "do", NORM: "do", TAG: "VBD"}, + {ORTH: "had", LEMMA: "have", NORM: "have", TAG: "VBD"}, + {ORTH: "may", NORM: "may", TAG: "MD"}, + {ORTH: "might", NORM: "might", TAG: "MD"}, + {ORTH: "must", NORM: "must", TAG: "MD"}, + {ORTH: "need", NORM: "need"}, + {ORTH: "ought", NORM: "ought", TAG: "MD"}, + {ORTH: "sha", LEMMA: "shall", NORM: "shall", TAG: "MD"}, + {ORTH: "should", NORM: "should", TAG: "MD"}, + {ORTH: "wo", LEMMA: "will", NORM: "will", TAG: "MD"}, + {ORTH: "would", NORM: "would", TAG: "MD"}]: verb_data_tc = dict(verb_data) verb_data_tc[ORTH] = verb_data_tc[ORTH].title() for data in [verb_data, verb_data_tc]: _exc[data[ORTH] + "n't"] = [ dict(data), - {ORTH: "n't", LEMMA: "not", TAG: "RB"}] + {ORTH: "n't", LEMMA: "not", NORM: "not", TAG: "RB"}] _exc[data[ORTH] + "nt"] = [ dict(data), - {ORTH: "nt", LEMMA: "not", TAG: "RB"}] + {ORTH: "nt", LEMMA: "not", NORM: "not", TAG: "RB"}] _exc[data[ORTH] + "n't've"] = [ dict(data), - {ORTH: "n't", LEMMA: "not", TAG: "RB"}, - {ORTH: "'ve", LEMMA: "have", TAG: "VB"}] + {ORTH: "n't", LEMMA: "not", NORM: "not", TAG: "RB"}, + {ORTH: "'ve", LEMMA: "have", NORM: "have", TAG: "VB"}] _exc[data[ORTH] + "ntve"] = [ dict(data), - {ORTH: "nt", LEMMA: "not", TAG: "RB"}, - {ORTH: "ve", LEMMA: "have", TAG: "VB"}] + {ORTH: "nt", LEMMA: "not", NORM: "not", TAG: "RB"}, + {ORTH: "ve", LEMMA: "have", NORM: "have", TAG: "VB"}] for verb_data in [ - {ORTH: "could", TAG: "MD"}, - {ORTH: "might"}, - {ORTH: "must"}, - {ORTH: "should"}]: + {ORTH: "could", NORM: "could", TAG: "MD"}, + {ORTH: "might", NORM: "might", TAG: "MD"}, + {ORTH: "must", NORM: "must", TAG: "MD"}, + {ORTH: "should", NORM: "should", TAG: "MD"}]: verb_data_tc = dict(verb_data) verb_data_tc[ORTH] = verb_data_tc[ORTH].title() for data in [verb_data, verb_data_tc]: @@ -228,21 +228,21 @@ for verb_data in [ for verb_data in [ - {ORTH: "ai", TAG: "VBP", "number": 2, LEMMA: "be"}, - {ORTH: "are", LEMMA: "be", TAG: "VBP", "number": 2}, - {ORTH: "is", LEMMA: "be", TAG: "VBZ"}, - {ORTH: "was", LEMMA: "be"}, - {ORTH: "were", LEMMA: "be"}]: + {ORTH: "ai", LEMMA: "be", TAG: "VBP", "number": 2}, + {ORTH: "are", LEMMA: "be", NORM: "are", TAG: "VBP", "number": 2}, + {ORTH: "is", LEMMA: "be", NORM: "is", TAG: "VBZ"}, + {ORTH: "was", LEMMA: "be", NORM: "was"}, + {ORTH: "were", LEMMA: "be", NORM: "were"}]: verb_data_tc = dict(verb_data) verb_data_tc[ORTH] = verb_data_tc[ORTH].title() for data in [verb_data, verb_data_tc]: _exc[data[ORTH] + "n't"] = [ dict(data), - {ORTH: "n't", LEMMA: "not", TAG: "RB"}] + {ORTH: "n't", LEMMA: "not", NORM: "not", TAG: "RB"}] _exc[data[ORTH] + "nt"] = [ dict(data), - {ORTH: "nt", LEMMA: "not", TAG: "RB"}] + {ORTH: "nt", LEMMA: "not", NORM: "not", TAG: "RB"}] # Other contractions with trailing apostrophe @@ -250,10 +250,10 @@ for verb_data in [ for exc_data in [ {ORTH: "doin", LEMMA: "do", NORM: "doing"}, {ORTH: "goin", LEMMA: "go", NORM: "going"}, - {ORTH: "nothin", LEMMA: "nothing"}, - {ORTH: "nuthin", LEMMA: "nothing"}, - {ORTH: "ol", LEMMA: "old"}, - {ORTH: "somethin", LEMMA: "something"}]: + {ORTH: "nothin", LEMMA: "nothing", NORM: "nothing"}, + {ORTH: "nuthin", LEMMA: "nothing", NORM: "nothing"}, + {ORTH: "ol", LEMMA: "old", NORM: "old"}, + {ORTH: "somethin", LEMMA: "something", NORM: "something"}]: exc_data_tc = dict(exc_data) exc_data_tc[ORTH] = exc_data_tc[ORTH].title() for data in [exc_data, exc_data_tc]: @@ -266,10 +266,10 @@ for exc_data in [ # Other contractions with leading apostrophe for exc_data in [ - {ORTH: "cause", LEMMA: "because"}, + {ORTH: "cause", LEMMA: "because", NORM: "because"}, {ORTH: "em", LEMMA: PRON_LEMMA, NORM: "them"}, - {ORTH: "ll", LEMMA: "will"}, - {ORTH: "nuff", LEMMA: "enough"}]: + {ORTH: "ll", LEMMA: "will", NORM: "will"}, + {ORTH: "nuff", LEMMA: "enough", NORM: "enough"}]: exc_data_apos = dict(exc_data) exc_data_apos[ORTH] = "'" + exc_data_apos[ORTH] for data in [exc_data, exc_data_apos]: @@ -282,11 +282,11 @@ for h in range(1, 12 + 1): for period in ["a.m.", "am"]: _exc["%d%s" % (h, period)] = [ {ORTH: "%d" % h}, - {ORTH: period, LEMMA: "a.m."}] + {ORTH: period, LEMMA: "a.m.", NORM: "a.m."}] for period in ["p.m.", "pm"]: _exc["%d%s" % (h, period)] = [ {ORTH: "%d" % h}, - {ORTH: period, LEMMA: "p.m."}] + {ORTH: period, LEMMA: "p.m.", NORM: "p.m."}] # Rest @@ -306,56 +306,56 @@ _other_exc = { {ORTH: "'y", LEMMA: PRON_LEMMA, NORM: "you"}], "How'd'y": [ - {ORTH: "How", LEMMA: "how"}, + {ORTH: "How", LEMMA: "how", NORM: "how"}, {ORTH: "'d", LEMMA: "do"}, {ORTH: "'y", LEMMA: PRON_LEMMA, NORM: "you"}], "not've": [ {ORTH: "not", LEMMA: "not", TAG: "RB"}, - {ORTH: "'ve", LEMMA: "have", TAG: "VB"}], + {ORTH: "'ve", LEMMA: "have", NORM: "have", TAG: "VB"}], "notve": [ {ORTH: "not", LEMMA: "not", TAG: "RB"}, - {ORTH: "ve", LEMMA: "have", TAG: "VB"}], + {ORTH: "ve", LEMMA: "have", NORM: "have", TAG: "VB"}], "Not've": [ - {ORTH: "Not", LEMMA: "not", TAG: "RB"}, - {ORTH: "'ve", LEMMA: "have", TAG: "VB"}], + {ORTH: "Not", LEMMA: "not", NORM: "not", TAG: "RB"}, + {ORTH: "'ve", LEMMA: "have", NORM: "have", TAG: "VB"}], "Notve": [ - {ORTH: "Not", LEMMA: "not", TAG: "RB"}, - {ORTH: "ve", LEMMA: "have", TAG: "VB"}], + {ORTH: "Not", LEMMA: "not", NORM: "not", TAG: "RB"}, + {ORTH: "ve", LEMMA: "have", NORM: "have", TAG: "VB"}], "cannot": [ {ORTH: "can", LEMMA: "can", TAG: "MD"}, {ORTH: "not", LEMMA: "not", TAG: "RB"}], "Cannot": [ - {ORTH: "Can", LEMMA: "can", TAG: "MD"}, + {ORTH: "Can", LEMMA: "can", NORM: "can", TAG: "MD"}, {ORTH: "not", LEMMA: "not", TAG: "RB"}], "gonna": [ {ORTH: "gon", LEMMA: "go", NORM: "going"}, - {ORTH: "na", LEMMA: "to"}], + {ORTH: "na", LEMMA: "to", NORM: "to"}], "Gonna": [ {ORTH: "Gon", LEMMA: "go", NORM: "going"}, - {ORTH: "na", LEMMA: "to"}], + {ORTH: "na", LEMMA: "to", NORM: "to"}], "gotta": [ {ORTH: "got"}, - {ORTH: "ta", LEMMA: "to"}], + {ORTH: "ta", LEMMA: "to", NORM: "to"}], "Gotta": [ - {ORTH: "Got"}, - {ORTH: "ta", LEMMA: "to"}], + {ORTH: "Got", NORM: "got"}, + {ORTH: "ta", LEMMA: "to", NORM: "to"}], "let's": [ {ORTH: "let"}, {ORTH: "'s", LEMMA: PRON_LEMMA, NORM: "us"}], "Let's": [ - {ORTH: "Let", LEMMA: "let"}, + {ORTH: "Let", LEMMA: "let", NORM: "let"}, {ORTH: "'s", LEMMA: PRON_LEMMA, NORM: "us"}] } @@ -363,72 +363,80 @@ _exc.update(_other_exc) for exc_data in [ - {ORTH: "'S", LEMMA: "'s"}, - {ORTH: "'s", LEMMA: "'s"}, - {ORTH: "\u2018S", LEMMA: "'s"}, - {ORTH: "\u2018s", LEMMA: "'s"}, - {ORTH: "and/or", LEMMA: "and/or", TAG: "CC"}, + {ORTH: "'S", LEMMA: "'s", NORM: "'s"}, + {ORTH: "'s", LEMMA: "'s", NORM: "'s"}, + {ORTH: "\u2018S", LEMMA: "'s", NORM: "'s"}, + {ORTH: "\u2018s", LEMMA: "'s", NORM: "'s"}, + {ORTH: "and/or", LEMMA: "and/or", NORM: "and/or", TAG: "CC"}, + {ORTH: "w/o", LEMMA: "without", NORM: "without"}, {ORTH: "'re", LEMMA: "be", NORM: "are"}, - {ORTH: "'Cause", LEMMA: "because"}, - {ORTH: "'cause", LEMMA: "because"}, - {ORTH: "ma'am", LEMMA: "madam"}, - {ORTH: "Ma'am", LEMMA: "madam"}, - {ORTH: "o'clock", LEMMA: "o'clock"}, - {ORTH: "O'clock", LEMMA: "o'clock"}, + {ORTH: "'Cause", LEMMA: "because", NORM: "because"}, + {ORTH: "'cause", LEMMA: "because", NORM: "because"}, + {ORTH: "'cos", LEMMA: "because", NORM: "because"}, + {ORTH: "'Cos", LEMMA: "because", NORM: "because"}, + {ORTH: "'coz", LEMMA: "because", NORM: "because"}, + {ORTH: "'Coz", LEMMA: "because", NORM: "because"}, + {ORTH: "'cuz", LEMMA: "because", NORM: "because"}, + {ORTH: "'Cuz", LEMMA: "because", NORM: "because"}, + {ORTH: "'bout", LEMMA: "about", NORM: "about"}, + {ORTH: "ma'am", LEMMA: "madam", NORM: "madam"}, + {ORTH: "Ma'am", LEMMA: "madam", NORM: "madam"}, + {ORTH: "o'clock", LEMMA: "o'clock", NORM: "o'clock"}, + {ORTH: "O'clock", LEMMA: "o'clock", NORM: "o'clock"}, - {ORTH: "Mt.", LEMMA: "Mount"}, - {ORTH: "Ak.", LEMMA: "Alaska"}, - {ORTH: "Ala.", LEMMA: "Alabama"}, - {ORTH: "Apr.", LEMMA: "April"}, - {ORTH: "Ariz.", LEMMA: "Arizona"}, - {ORTH: "Ark.", LEMMA: "Arkansas"}, - {ORTH: "Aug.", LEMMA: "August"}, - {ORTH: "Calif.", LEMMA: "California"}, - {ORTH: "Colo.", LEMMA: "Colorado"}, - {ORTH: "Conn.", LEMMA: "Connecticut"}, - {ORTH: "Dec.", LEMMA: "December"}, - {ORTH: "Del.", LEMMA: "Delaware"}, - {ORTH: "Feb.", LEMMA: "February"}, - {ORTH: "Fla.", LEMMA: "Florida"}, - {ORTH: "Ga.", LEMMA: "Georgia"}, - {ORTH: "Ia.", LEMMA: "Iowa"}, - {ORTH: "Id.", LEMMA: "Idaho"}, - {ORTH: "Ill.", LEMMA: "Illinois"}, - {ORTH: "Ind.", LEMMA: "Indiana"}, - {ORTH: "Jan.", LEMMA: "January"}, - {ORTH: "Jul.", LEMMA: "July"}, - {ORTH: "Jun.", LEMMA: "June"}, - {ORTH: "Kan.", LEMMA: "Kansas"}, - {ORTH: "Kans.", LEMMA: "Kansas"}, - {ORTH: "Ky.", LEMMA: "Kentucky"}, - {ORTH: "La.", LEMMA: "Louisiana"}, - {ORTH: "Mar.", LEMMA: "March"}, - {ORTH: "Mass.", LEMMA: "Massachusetts"}, - {ORTH: "May.", LEMMA: "May"}, - {ORTH: "Mich.", LEMMA: "Michigan"}, - {ORTH: "Minn.", LEMMA: "Minnesota"}, - {ORTH: "Miss.", LEMMA: "Mississippi"}, - {ORTH: "N.C.", LEMMA: "North Carolina"}, - {ORTH: "N.D.", LEMMA: "North Dakota"}, - {ORTH: "N.H.", LEMMA: "New Hampshire"}, - {ORTH: "N.J.", LEMMA: "New Jersey"}, - {ORTH: "N.M.", LEMMA: "New Mexico"}, - {ORTH: "N.Y.", LEMMA: "New York"}, - {ORTH: "Neb.", LEMMA: "Nebraska"}, - {ORTH: "Nebr.", LEMMA: "Nebraska"}, - {ORTH: "Nev.", LEMMA: "Nevada"}, - {ORTH: "Nov.", LEMMA: "November"}, - {ORTH: "Oct.", LEMMA: "October"}, - {ORTH: "Okla.", LEMMA: "Oklahoma"}, - {ORTH: "Ore.", LEMMA: "Oregon"}, - {ORTH: "Pa.", LEMMA: "Pennsylvania"}, - {ORTH: "S.C.", LEMMA: "South Carolina"}, - {ORTH: "Sep.", LEMMA: "September"}, - {ORTH: "Sept.", LEMMA: "September"}, - {ORTH: "Tenn.", LEMMA: "Tennessee"}, - {ORTH: "Va.", LEMMA: "Virginia"}, - {ORTH: "Wash.", LEMMA: "Washington"}, - {ORTH: "Wis.", LEMMA: "Wisconsin"}]: + {ORTH: "Mt.", LEMMA: "Mount", NORM: "Mount"}, + {ORTH: "Ak.", LEMMA: "Alaska", NORM: "Alaska"}, + {ORTH: "Ala.", LEMMA: "Alabama", NORM: "Alabama"}, + {ORTH: "Apr.", LEMMA: "April", NORM: "April"}, + {ORTH: "Ariz.", LEMMA: "Arizona", NORM: "Arizona"}, + {ORTH: "Ark.", LEMMA: "Arkansas", NORM: "Arkansas"}, + {ORTH: "Aug.", LEMMA: "August", NORM: "August"}, + {ORTH: "Calif.", LEMMA: "California", NORM: "California"}, + {ORTH: "Colo.", LEMMA: "Colorado", NORM: "Colorado"}, + {ORTH: "Conn.", LEMMA: "Connecticut", NORM: "Connecticut"}, + {ORTH: "Dec.", LEMMA: "December", NORM: "December"}, + {ORTH: "Del.", LEMMA: "Delaware", NORM: "Delaware"}, + {ORTH: "Feb.", LEMMA: "February", NORM: "February"}, + {ORTH: "Fla.", LEMMA: "Florida", NORM: "Florida"}, + {ORTH: "Ga.", LEMMA: "Georgia", NORM: "Georgia"}, + {ORTH: "Ia.", LEMMA: "Iowa", NORM: "Iowa"}, + {ORTH: "Id.", LEMMA: "Idaho", NORM: "Idaho"}, + {ORTH: "Ill.", LEMMA: "Illinois", NORM: "Illinois"}, + {ORTH: "Ind.", LEMMA: "Indiana", NORM: "Indiana"}, + {ORTH: "Jan.", LEMMA: "January", NORM: "January"}, + {ORTH: "Jul.", LEMMA: "July", NORM: "July"}, + {ORTH: "Jun.", LEMMA: "June", NORM: "June"}, + {ORTH: "Kan.", LEMMA: "Kansas", NORM: "Kansas"}, + {ORTH: "Kans.", LEMMA: "Kansas", NORM: "Kansas"}, + {ORTH: "Ky.", LEMMA: "Kentucky", NORM: "Kentucky"}, + {ORTH: "La.", LEMMA: "Louisiana", NORM: "Louisiana"}, + {ORTH: "Mar.", LEMMA: "March", NORM: "March"}, + {ORTH: "Mass.", LEMMA: "Massachusetts", NORM: "Massachusetts"}, + {ORTH: "May.", LEMMA: "May", NORM: "May"}, + {ORTH: "Mich.", LEMMA: "Michigan", NORM: "Michigan"}, + {ORTH: "Minn.", LEMMA: "Minnesota", NORM: "Minnesota"}, + {ORTH: "Miss.", LEMMA: "Mississippi", NORM: "Mississippi"}, + {ORTH: "N.C.", LEMMA: "North Carolina", NORM: "North Carolina"}, + {ORTH: "N.D.", LEMMA: "North Dakota", NORM: "North Dakota"}, + {ORTH: "N.H.", LEMMA: "New Hampshire", NORM: "New Hampshire"}, + {ORTH: "N.J.", LEMMA: "New Jersey", NORM: "New Jersey"}, + {ORTH: "N.M.", LEMMA: "New Mexico", NORM: "New Mexico"}, + {ORTH: "N.Y.", LEMMA: "New York", NORM: "New York"}, + {ORTH: "Neb.", LEMMA: "Nebraska", NORM: "Nebraska"}, + {ORTH: "Nebr.", LEMMA: "Nebraska", NORM: "Nebraska"}, + {ORTH: "Nev.", LEMMA: "Nevada", NORM: "Nevada"}, + {ORTH: "Nov.", LEMMA: "November", NORM: "November"}, + {ORTH: "Oct.", LEMMA: "October", NORM: "October"}, + {ORTH: "Okla.", LEMMA: "Oklahoma", NORM: "Oklahoma"}, + {ORTH: "Ore.", LEMMA: "Oregon", NORM: "Oregon"}, + {ORTH: "Pa.", LEMMA: "Pennsylvania", NORM: "Pennsylvania"}, + {ORTH: "S.C.", LEMMA: "South Carolina", NORM: "South Carolina"}, + {ORTH: "Sep.", LEMMA: "September", NORM: "September"}, + {ORTH: "Sept.", LEMMA: "September", NORM: "September"}, + {ORTH: "Tenn.", LEMMA: "Tennessee", NORM: "Tennessee"}, + {ORTH: "Va.", LEMMA: "Virginia", NORM: "Virginia"}, + {ORTH: "Wash.", LEMMA: "Washington", NORM: "Washington"}, + {ORTH: "Wis.", LEMMA: "Wisconsin", NORM: "Wisconsin"}]: _exc[exc_data[ORTH]] = [dict(exc_data)] diff --git a/spacy/lang/es/__init__.py b/spacy/lang/es/__init__.py index f5735d460..1e7f55be8 100644 --- a/spacy/lang/es/__init__.py +++ b/spacy/lang/es/__init__.py @@ -5,21 +5,25 @@ from .tokenizer_exceptions import TOKENIZER_EXCEPTIONS from .tag_map import TAG_MAP from .stop_words import STOP_WORDS from .lemmatizer import LOOKUP +from .syntax_iterators import SYNTAX_ITERATORS from ..tokenizer_exceptions import BASE_EXCEPTIONS +from ..norm_exceptions import BASE_NORMS from ...language import Language from ...lemmatizerlookup import Lemmatizer -from ...attrs import LANG -from ...util import update_exc +from ...attrs import LANG, NORM +from ...util import update_exc, add_lookups class SpanishDefaults(Language.Defaults): lex_attr_getters = dict(Language.Defaults.lex_attr_getters) lex_attr_getters[LANG] = lambda text: 'es' + lex_attr_getters[NORM] = add_lookups(Language.Defaults.lex_attr_getters[NORM], BASE_NORMS) tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) tag_map = dict(TAG_MAP) stop_words = set(STOP_WORDS) + sytax_iterators = dict(SYNTAX_ITERATORS) @classmethod def create_lemmatizer(cls, nlp=None): @@ -28,7 +32,7 @@ class SpanishDefaults(Language.Defaults): class Spanish(Language): lang = 'es' - Defaults = SpanishDefaults + __all__ = ['Spanish'] diff --git a/spacy/lang/es/syntax_iterators.py b/spacy/lang/es/syntax_iterators.py new file mode 100644 index 000000000..c414897a0 --- /dev/null +++ b/spacy/lang/es/syntax_iterators.py @@ -0,0 +1,55 @@ +# coding: utf8 +from __future__ import unicode_literals + +from ...symbols import NOUN, PROPN, PRON, VERB, AUX + + +def noun_chunks(obj): + doc = obj.doc + np_label = doc.vocab.strings['NP'] + left_labels = ['det', 'fixed', 'neg'] #['nunmod', 'det', 'appos', 'fixed'] + right_labels = ['flat', 'fixed', 'compound', 'neg'] + stop_labels = ['punct'] + np_left_deps = [doc.vocab.strings[label] for label in left_labels] + np_right_deps = [doc.vocab.strings[label] for label in right_labels] + stop_deps = [doc.vocab.strings[label] for label in stop_labels] + token = doc[0] + while token and token.i < len(doc): + if token.pos in [PROPN, NOUN, PRON]: + left, right = noun_bounds(token) + yield left.i, right.i+1, np_label + token = right + token = next_token(token) + + +def is_verb_token(token): + return token.pos in [VERB, AUX] + + +def next_token(token): + try: + return token.nbor() + except: + return None + + +def noun_bounds(root): + left_bound = root + for token in reversed(list(root.lefts)): + if token.dep in np_left_deps: + left_bound = token + right_bound = root + for token in root.rights: + if (token.dep in np_right_deps): + left, right = noun_bounds(token) + if list(filter(lambda t: is_verb_token(t) or t.dep in stop_deps, + doc[left_bound.i: right.i])): + break + else: + right_bound = right + return left_bound, right_bound + + +SYNTAX_ITERATORS = { + 'noun_chunks': noun_chunks +} diff --git a/spacy/lang/es/tokenizer_exceptions.py b/spacy/lang/es/tokenizer_exceptions.py index 262089494..77d9a2841 100644 --- a/spacy/lang/es/tokenizer_exceptions.py +++ b/spacy/lang/es/tokenizer_exceptions.py @@ -6,37 +6,13 @@ from ...deprecated import PRON_LEMMA _exc = { - "al": [ - {ORTH: "a", LEMMA: "a", TAG: ADP}, - {ORTH: "l", LEMMA: "el", TAG: DET}], - - "consigo": [ - {ORTH: "con", LEMMA: "con"}, - {ORTH: "sigo", LEMMA: PRON_LEMMA, NORM: "sí"}], - - "conmigo": [ - {ORTH: "con", LEMMA: "con"}, - {ORTH: "migo", LEMMA: PRON_LEMMA, NORM: "mí"}], - - "contigo": [ - {ORTH: "con", LEMMA: "con"}, - {ORTH: "tigo", LEMMA: PRON_LEMMA, NORM: "ti"}], - - "del": [ - {ORTH: "de", LEMMA: "de", TAG: ADP}, - {ORTH: "l", LEMMA: "el", TAG: DET}], - - "pel": [ - {ORTH: "pe", LEMMA: "per", TAG: ADP}, - {ORTH: "l", LEMMA: "el", TAG: DET}], - "pal": [ {ORTH: "pa", LEMMA: "para"}, - {ORTH: "l", LEMMA: "el"}], + {ORTH: "l", LEMMA: "el", NORM: "el"}], "pala": [ {ORTH: "pa", LEMMA: "para"}, - {ORTH: "la"}] + {ORTH: "la", LEMMA: "la", NORM: "la"}] } diff --git a/spacy/lang/fi/__init__.py b/spacy/lang/fi/__init__.py index 8cb6ad8ab..931ad5341 100644 --- a/spacy/lang/fi/__init__.py +++ b/spacy/lang/fi/__init__.py @@ -5,20 +5,24 @@ from .tokenizer_exceptions import TOKENIZER_EXCEPTIONS from .stop_words import STOP_WORDS from ..tokenizer_exceptions import BASE_EXCEPTIONS +from ..norm_exceptions import BASE_NORMS from ...language import Language -from ...attrs import LANG -from ...util import update_exc +from ...attrs import LANG, NORM +from ...util import update_exc, add_lookups + + +class FinnishDefaults(Language.Defaults): + lex_attr_getters = dict(Language.Defaults.lex_attr_getters) + lex_attr_getters[LANG] = lambda text: 'fi' + lex_attr_getters[NORM] = add_lookups(Language.Defaults.lex_attr_getters[NORM], BASE_NORMS) + + tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) + stop_words = set(STOP_WORDS) class Finnish(Language): lang = 'fi' - - class Defaults(Language.Defaults): - lex_attr_getters = dict(Language.Defaults.lex_attr_getters) - lex_attr_getters[LANG] = lambda text: 'fi' - - tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) - stop_words = set(STOP_WORDS) + Defaults = FinnishDefaults __all__ = ['Finnish'] diff --git a/spacy/lang/fr/__init__.py b/spacy/lang/fr/__init__.py index a8a18a601..a243b6268 100644 --- a/spacy/lang/fr/__init__.py +++ b/spacy/lang/fr/__init__.py @@ -5,30 +5,36 @@ from .tokenizer_exceptions import TOKENIZER_EXCEPTIONS, TOKEN_MATCH from .punctuation import TOKENIZER_SUFFIXES, TOKENIZER_INFIXES from .stop_words import STOP_WORDS from .lemmatizer import LOOKUP +from .syntax_iterators import SYNTAX_ITERATORS from ..tokenizer_exceptions import BASE_EXCEPTIONS +from ..norm_exceptions import BASE_NORMS from ...language import Language from ...lemmatizerlookup import Lemmatizer -from ...attrs import LANG -from ...util import update_exc +from ...attrs import LANG, NORM +from ...util import update_exc, add_lookups + + +class FrenchDefaults(Language.Defaults): + lex_attr_getters = dict(Language.Defaults.lex_attr_getters) + lex_attr_getters[LANG] = lambda text: 'fr' + lex_attr_getters[NORM] = add_lookups(Language.Defaults.lex_attr_getters[NORM], BASE_NORMS) + + tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) + stop_words = set(STOP_WORDS) + infixes = tuple(TOKENIZER_INFIXES) + suffixes = tuple(TOKENIZER_SUFFIXES) + token_match = TOKEN_MATCH + syntax_iterators = dict(SYNTAX_ITERATORS) + + @classmethod + def create_lemmatizer(cls, nlp=None): + return Lemmatizer(LOOKUP) class French(Language): lang = 'fr' - - class Defaults(Language.Defaults): - lex_attr_getters = dict(Language.Defaults.lex_attr_getters) - lex_attr_getters[LANG] = lambda text: 'fr' - - tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) - stop_words = set(STOP_WORDS) - infixes = tuple(TOKENIZER_INFIXES) - suffixes = tuple(TOKENIZER_SUFFIXES) - token_match = TOKEN_MATCH - - @classmethod - def create_lemmatizer(cls, nlp=None): - return Lemmatizer(LOOKUP) + Defaults = FrenchDefaults __all__ = ['French'] diff --git a/spacy/lang/fr/_tokenizer_exceptions_list.py b/spacy/lang/fr/_tokenizer_exceptions_list.py index 37df0ba16..eb6d5bbce 100644 --- a/spacy/lang/fr/_tokenizer_exceptions_list.py +++ b/spacy/lang/fr/_tokenizer_exceptions_list.py @@ -1,42 +1,44 @@ # coding: utf8 from __future__ import unicode_literals - FR_BASE_EXCEPTIONS = [ +"(+)-amphétamine", +"(5R,6S)-7,8-didehydro-4,5-époxy-3-méthoxy-N-méthylmorphinan-6-ol", +"(R)-amphétamine", +"(S)-amphétamine", +"(−)-amphétamine", "0-day", "0-days", +"1,1-diméthylhydrazine", +"1,2,3-tris-nitrooxy-propane", +"1,2-diazine", +"1,2-dichloropropane", +"1,3-diazine", +"1,3-dichloropropène", +"1,4-diazine", +"1-DDOL", +"1-TDOL", +"1-alpha,2-alpha,3-bêta,4-alpha,5-alpha,6-bêta-hexachlorocyclohexane", +"1-dodécanol", +"1-méthyl-2,4,6-trinitrobenzène", +"1-tétradécanol", "1000Base-T", "100Base-T", "100Base-T4", "100Base-TX", "10BASE-F", "10Base-T", -"1,1-diméthylhydrazine", -"11-septembre", "11-Septembre", +"11-septembre", "120-cellules", -"1,2,3-tris-nitrooxy-propane", -"1,2-diazine", -"1,2-dichloropropane", -"1,3-diazine", -"1,3-dichloropropène", "14-18", -"1,4-diazine", "16-cellules", -"1-alpha,2-alpha,3-bêta,4-alpha,5-alpha,6-bêta-hexachlorocyclohexane", -"1-DDOL", -"1-dodécanol", -"1-méthyl-2,4,6-trinitrobenzène", -"1-TDOL", -"1-tétradécanol", "1T-SRAM", -"22-dihydroergocalciférol", "2,2'-iminodi(éthylamine)", "2,3,6-TBA", "2,4,5-T", "2,4,5-TP", "2,4,6-trinitrophénol", -"24-cellules", "2,4-D", "2,4-DB", "2,4-DP", @@ -45,12 +47,13 @@ FR_BASE_EXCEPTIONS = [ "2-désoxyribose", "2-méthylpropane", "2-méthylpropanes", +"22-dihydroergocalciférol", +"24-cellules", "2′-O-méthyla", "2′-O-méthylai", "2′-O-méthylaient", "2′-O-méthylais", "2′-O-méthylait", -"2′-O-méthylâmes", "2′-O-méthylant", "2′-O-méthylas", "2′-O-méthylasse", @@ -58,12 +61,7 @@ FR_BASE_EXCEPTIONS = [ "2′-O-méthylasses", "2′-O-méthylassiez", "2′-O-méthylassions", -"2′-O-méthylât", -"2′-O-méthylâtes", "2′-O-méthyle", -"2′-O-méthylé", -"2′-O-méthylée", -"2′-O-méthylées", "2′-O-méthylent", "2′-O-méthyler", "2′-O-méthylera", @@ -72,103 +70,79 @@ FR_BASE_EXCEPTIONS = [ "2′-O-méthylerais", "2′-O-méthylerait", "2′-O-méthyleras", -"2′-O-méthylèrent", "2′-O-méthylerez", "2′-O-méthyleriez", "2′-O-méthylerions", "2′-O-méthylerons", "2′-O-méthyleront", "2′-O-méthyles", -"2′-O-méthylés", "2′-O-méthylez", "2′-O-méthyliez", "2′-O-méthylions", "2′-O-méthylons", -"33-tours", +"2′-O-méthylâmes", +"2′-O-méthylât", +"2′-O-méthylâtes", +"2′-O-méthylèrent", +"2′-O-méthylé", +"2′-O-méthylée", +"2′-O-méthylées", +"2′-O-méthylés", "3,4-DCPA", "3,6-DCP", -"39-45", "3-hydroxyflavone", "3-méthylmorphine", +"33-tours", +"39-45", "4-3-3", "4-5-1", -"4-acétylaminophénol", "4-CPA", +"4-acétylaminophénol", "5-4-1", -"5-cellules", "5-HPETE", -"(5R,6S)-7,8-didehydro-4,5-époxy-3-méthoxy-N-méthylmorphinan-6-ol", -"600-cellules", +"5-cellules", "6-benzyladénine", +"600-cellules", "8-hydroxyquinoléine", "9-2", "9-3", +"A-EF", +"A-OF", +"A-ÉF", +"A.-Vict.", "AAAA-MM-JJ", "Aarle-Rixtel", -"abaisse-langue", -"abaisse-langues", "Abanto-Zierbena", "Abaucourt-Hautecourt", "Abbans-Dessous", "Abbans-Dessus", "Abbaye-sous-Plancy", +"Abbeville-Saint-Lucien", "Abbéville-la-Rivière", "Abbéville-lès-Conflans", -"Abbeville-Saint-Lucien", "Abcoude-Baambrugge", "Abcoude-Proostdij", "Abel-François", "Abergement-Clémenciat", +"Abergement-Saint-Jean", +"Abergement-Sainte-Colombe", "Abergement-de-Cuisery", "Abergement-de-Varey", "Abergement-la-Ronce", "Abergement-le-Grand", "Abergement-le-Petit", "Abergement-lès-Thésy", -"Abergement-Sainte-Colombe", -"Abergement-Saint-Jean", "Abitibi-Témiscamien", "Abitibi-Témiscamingue", "Abjat-sur-Bandiat", -"Ablaincourt-Pressoir", "Ablain-Saint-Nazaire", +"Ablaincourt-Pressoir", "Ablon-sur-Seine", "Aboncourt-Gesincourt", "Aboncourt-sur-Seille", -"abou-hannès", -"abou-mengel", -"abou-mengels", -"abricotier-pays", -"abricot-pêche", -"abricots-pêches", -"abri-sous-roche", -"abris-sous-roche", -"abris-vent", -"abri-vent", -"absorbeur-neutralisateur", -"acajou-amer", -"acajou-bois", -"acajous-amers", -"acajous-bois", -"accord-cadre", -"accords-cadres", -"accroche-coeur", -"accroche-cœur", -"accroche-coeurs", -"accroche-cœurs", -"accroche-pied", -"accroche-pieds", -"accroche-plat", -"accroche-plats", -"acétyl-salicylate", -"acétyl-salicylates", -"achard-bourgeois", "Achard-Bourgeois", -"achard-bourgeoise", "Achard-Bourgeoise", -"achard-bourgeoises", "Achard-Bourgeoises", -"Achères-la-Forêt", "Acheux-en-Amiénois", "Acheux-en-Vimeu", "Achiet-le-Grand", @@ -176,168 +150,65 @@ FR_BASE_EXCEPTIONS = [ "Achter-Drempt", "Achter-Lindt", "Achter-Thesinge", -"acibenzolar-S-méthyle", -"acide-N-1-naphtyl-phtalamique", -"acide-phénol", -"acides-phénols", -"acido-alcalimétrie", -"acido-alcoolo-résistance", -"acido-alcoolo-résistances", -"acido-alcoolo-résistant", -"acido-alcoolo-résistante", -"acido-alcoolo-résistantes", -"acido-alcoolo-résistants", -"acido-basique", -"acido-résistant", -"acido-résistants", -"acquae-sextien", +"Achères-la-Forêt", "Acquae-Sextien", -"acquae-sextienne", "Acquae-Sextienne", -"acquae-sextiennes", "Acquae-Sextiennes", -"acquae-sextiens", "Acquae-Sextiens", -"acqua-toffana", -"acqua-toffanas", "Acquin-Westbécourt", -"acquit-à-caution", -"acquit-patent", -"acquits-à-caution", -"acquits-patents", -"acting-out", -"actino-uranium", -"Acy-en-Multien", "Acy-Romance", +"Acy-en-Multien", +"Ad-Dawr", "Adam-lès-Passavant", "Adam-lès-Vercel", -"Ad-Dawr", "Addis-Abeba", "Addis-Abebien", "Addis-Abébien", -"add-on", "Adelans-et-le-Val-de-Bithaine", "Adervielle-Pouchergues", -"adieu-mes-couilles", -"adieu-tout", -"adieu-touts", -"adieu-va", -"adieu-vas", -"adieu-vat", -"adieu-vats", -"adiposo-génital", -"adiposo-génitale", -"adiposo-génitales", -"adiposo-génitaux", -"adjudant-chef", -"adjudants-chefs", "Admannshagen-Bargeshagen", "Adrets-de-Fréjus", "Adwick-le-Street", -"A-EF", -"A-ÉF", -"africain-américain", "Africain-Américain", -"africaine-américaine", "Africaine-Américaine", -"africaines-américaines", "Africaines-Américaines", -"africains-américains", "Africains-Américains", -"africano-brésilien", -"africano-brésilienne", -"africano-brésiliennes", -"africano-brésiliens", -"africano-taïwanais", -"africano-taïwanaise", -"africano-taïwanaises", -"agace-pissette", -"agar-agar", -"agasse-tambourinette", -"agatha-christien", "Agatha-christien", "Agen-d'Aveyron", -"agit-prop", "Agnam-Goly", "Agnez-lès-Duisans", "Agnicourt-et-Séchelles", "Agnières-en-Dévoluy", -"agnus-castus", -"agnus-dei", "Agon-Coutainville", -"agora-phobie", -"agora-phobies", "Agos-Vidalos", "Ahaxe-Alciette-Bascassan", "Ahlefeld-Bistensee", "Ahrenshagen-Daskow", -"aï-aï", "Aibar-Oibar", "Aichach-Friedberg", -"ai-cham", -"Aïcirits-Camou-Suhast", -"aide-comptable", -"aide-écuyer", -"aide-écuyers", -"aide-éducateur", -"Aïd-el-Kébir", -"Aïd-el-Séghir", -"aide-mémoire", -"aide-mémoires", -"aide-soignant", -"aide-soignante", -"aide-soignantes", -"aide-soignants", -"aides-soignantes", -"aides-soignants", -"aigle-bar", "Aignay-le-Duc", "Aignes-et-Puypéroux", -"aigre-douce", -"aigre-doux", "Aigrefeuille-d'Aunis", "Aigrefeuille-sur-Maine", -"aigre-moines", -"aigres-douces", -"aigres-doux", "Aiguebelette-le-Lac", -"aigue-marine", -"aigue-marines", -"aigues-juntais", "Aigues-Juntais", -"aigues-juntaise", "Aigues-Juntaise", -"aigues-juntaises", "Aigues-Juntaises", "Aigues-Juntes", -"aigues-marines", -"aigues-mortais", "Aigues-Mortais", -"aigues-mortaise", "Aigues-Mortaise", -"aigues-mortaises", "Aigues-Mortaises", "Aigues-Mortes", "Aigues-Vives", -"aigues-vivesien", "Aigues-Vivesien", -"aigues-vivesienne", "Aigues-Vivesienne", -"aigues-vivesiennes", "Aigues-Vivesiennes", -"aigues-vivesiens", "Aigues-Vivesiens", -"aigues-vivien", -"aigues-vivois", "Aigues-Vivois", -"aigues-vivoise", "Aigues-Vivoise", -"aigues-vivoises", "Aigues-Vivoises", "Aiguillon-sur-Mer", "Aiguillon-sur-Vie", -"aiguise-crayon", -"aiguise-crayons", "Aillant-sur-Milleron", "Aillant-sur-Tholon", "Aillevillers-et-Lyaumont", @@ -352,18 +223,14 @@ FR_BASE_EXCEPTIONS = [ "Ainay-le-Château", "Ainay-le-Vieil", "Ainhice-Mongelos", -"Aínsa-Sobrarbe", -"ainu-ken", "Ainval-Septoutre", "Aire-la-Ville", -"airelle-myrtille", "Aire-sur-l'Adour", "Aire-sur-la-Lys", "Airon-Notre-Dame", "Airon-Saint-Vaast", "Aische-en-Refail", "Aiseau-Presles", -"aiseau-preslois", "Aiseau-Preslois", "Aiseau-Presloise", "Aisey-et-Richecourt", @@ -371,146 +238,85 @@ FR_BASE_EXCEPTIONS = [ "Aisonville-et-Bernoville", "Aisy-sous-Thil", "Aisy-sur-Armançon", +"Aix-Noulette", +"Aix-Villemaur-Pâlis", "Aix-en-Diois", "Aix-en-Ergny", "Aix-en-Issart", "Aix-en-Othe", "Aix-en-Provence", -"Aixe-sur-Vienne", "Aix-la-Chapelle", "Aix-la-Fayette", "Aix-les-Bains", -"Aix-Noulette", -"Aix-Villemaur-Pâlis", +"Aixe-sur-Vienne", "Aizecourt-le-Bas", "Aizecourt-le-Haut", "Aizy-Jouy", "Ajoupa-Bouillon", -"aka-bea", -"aka-bo", -"aka-cari", -"aka-jeru", -"aka-kede", -"aka-kora", -"akar-bale", -"akhal-teke", -"akua-ba", -"Alaincourt-la-Côte", -"al-Anbar", "Al-Anbar", -"al-Anbâr", "Al-Anbâr", -"al-Anbār", "Al-Andalus", -"Alba-la-Romaine", -"albano-letton", -"Albaret-le-Comtal", -"Albaret-Sainte-Marie", +"Al-Dour", +"Al-Khwarizmi", +"Al-Qaida", +"Al-Qaïda", +"Alaincourt-la-Côte", "Alb-Danube", +"Alba-la-Romaine", +"Albaret-Sainte-Marie", +"Albaret-le-Comtal", "Albefeuille-Lagarde", "Albepierre-Bredons", "Albergaria-a-Velha", -"Albiez-le-Jeune", "Albiez-Montrond", +"Albiez-le-Jeune", "Albigny-sur-Saône", "Albon-d'Ardèche", "Alby-sur-Chéran", -"alcalino-terreuse", -"alcalino-terreuses", -"alcalino-terreux", -"Alçay-Alçabéhéty-Sunharette", -"alcoolo-dépendance", -"alcoolo-dépendances", -"alcool-phénol", -"alcools-phénols", -"Al-Dour", "Aldridge-Brownhills", "Alegría-Dulantzi", -"aléseuse-fraiseuse", -"aléseuses-fraiseuses", "Alet-les-Bains", -"algéro-marocain", -"algéro-tuniso-lybien", -"algéro-tuniso-marocain", -"algo-carburant", -"algo-carburants", "Alignan-du-Vent", "Alise-Sainte-Reine", -"al-Kachi", -"Al-Khwarizmi", "Allaines-Mervilliers", "Allainville-aux-Bois", "Allainville-en-Beauce", "Alland'Huy", "Alland'Huy-et-Sausseuil", -"allanto-chorion", -"allanto-chorions", "Allas-Bocage", "Allas-Champagne", "Allas-les-Mines", -"Allègre-les-Fumades", "Allemagne-en-Provence", "Allemanche-Launay-et-Soyer", "Allemans-du-Dropt", "Allennes-les-Marais", "Allerey-sur-Saône", -"aller-retour", -"aller-retours", -"allers-retours", "Alles-sur-Dordogne", "Allez-et-Cazeneuve", -"allez-vous-en", -"allez-y", -"Allières-et-Risset", "Alligny-Cosne", "Alligny-en-Morvan", +"Allières-et-Risset", "Allondrelle-la-Malmaison", "Allonzier-la-Caille", "Allouville-Bellefosse", -"alloxydime-sodium", -"allume-cigare", -"allume-cigares", -"allume-feu", -"allume-feux", -"allume-gaz", -"allumette-bougie", -"allumettes-bougies", +"Allègre-les-Fumades", "Almon-les-Junies", "Almont-les-Junies", "Alos-Sibas-Abense", "Aloxe-Corton", -"Alpes-de-Haute-Provence", "Alpes-Maritimes", -"alpha-amylase", -"alpha-amylases", -"alpha-conversion", -"alpha-conversions", -"alpha-test", -"alpha-tests", -"alpha-tridymite", -"alpha-tridymites", -"alpha-variscite", -"alpha-variscites", +"Alpes-de-Haute-Provence", "Alphen-Boshoven", "Alphen-Chaam", "Alphen-Oosterwijk", "Alphen-sur-le-Rhin", -"al-Qaida", -"Al-Qaida", -"al-Qaïda", -"Al-Qaïda", "Alsace-Champagne-Ardenne-Lorraine", "Alsace-Lorraine", -"alsacien-lorrain", "Alsbach-Hähnlein", "Althen-des-Paluds", "Altmark-Salzwedel", -"alto-basso", -"alto-bassos", -"aluminium-épidote", -"aluminium-épidotes", -"alumu-tesu", "Alzey-Worms", +"Alçay-Alçabéhéty-Sunharette", "Amagne-Lucquy", "Amareins-Francheleins-Cesseins", "Amathay-Vésigneux", @@ -518,72 +324,32 @@ FR_BASE_EXCEPTIONS = [ "Amayé-sur-Seulles", "Ambarès-et-Lagrave", "Amberg-Sulzbach", -"Ambérieu-en-Bugey", -"Ambérieux-en-Dombes", "Ambillou-Château", "Amblans-et-Velotte", "Ambly-Fleury", "Ambly-sur-Aisne", "Ambly-sur-Meuse", -"ambre-gris", "Ambrières-les-Vallées", -"ambystomes-tigres", -"ambystome-tigre", -"Amélie-les-Bains", -"Amélie-les-Bains-Palalda", +"Ambérieu-en-Bugey", +"Ambérieux-en-Dombes", "Amel-sur-l'Etang", "Amel-sur-l'Étang", "Amendeuix-Oneix", -"âme-sœur", -"âmes-sœurs", +"Amfreville-Saint-Amand", "Amfreville-la-Campagne", "Amfreville-la-Mi-Voie", "Amfreville-les-Champs", -"Amfreville-Saint-Amand", "Amfreville-sous-les-Monts", "Amfreville-sur-Iton", -"ami-ami", -"amiante-ciment", "Amigny-Rouy", -"amino-acétique", -"amino-acide", -"amino-acides", "Amlikon-Bissegg", "Amont-et-Effreney", "Amorebieta-Etxano", "Amorots-Succos", -"amour-en-cage", -"amour-propre", -"amours-en-cage", -"amours-propres", -"ampère-heure", -"ampères-heures", -"(+)-amphétamine", -"(−)-amphétamine", -"Ampilly-les-Bordes", "Ampilly-le-Sec", -"ampli-syntoniseur", -"amuse-bouche", -"amuse-bouches", -"amuse-gueule", -"amuse-gueules", -"analyste-programmeur", -"analystes-programmeurs", -"ananas-bois", -"anarcho-capitalisme", -"anarcho-capitalismes", -"anarcho-fasciste", -"anarcho-fascistes", -"anarcho-punk", -"anarcho-punks", -"anarcho-syndicalisme", -"anarcho-syndicalismes", -"anarcho-syndicaliste", -"anarcho-syndicalistes", -"anatomo-pathologie", -"anatomo-pathologies", -"anatomo-pathologique", -"anatomo-pathologiques", +"Ampilly-les-Bordes", +"Amélie-les-Bains", +"Amélie-les-Bains-Palalda", "Ance-Féas", "Anchenoncourt-et-Chazel", "Ancourteville-sur-Héricourt", @@ -595,8 +361,8 @@ FR_BASE_EXCEPTIONS = [ "Ancy-le-Libre", "Ancy-sur-Moselle", "Andelot-Blancheville", -"Andelot-en-Montagne", "Andelot-Morval", +"Andelot-en-Montagne", "Andernos-les-Bains", "Andert-et-Condon", "Andilly-en-Bassigny", @@ -606,34 +372,27 @@ FR_BASE_EXCEPTIONS = [ "Andréenne-de-l'Est", "Andréennes-de-l'Est", "Andréens-de-l'Est", -"andrézien-bouthéonnais", "Andrézien-Bouthéonnais", -"andrézienne-bouthéonnaise", "Andrézienne-Bouthéonnaise", -"andréziennes-bouthéonnaises", "Andréziennes-Bouthéonnaises", -"andréziens-bouthéonnais", "Andréziens-Bouthéonnais", "Andrézieux-Bouthéon", -"Anéran-Camors", -"ânes-zèbres", -"âne-zèbre", -"Angeac-Champagne", -"Angeac-Charente", "Ange-Gardienois", "Ange-Gardienoise", +"Angeac-Champagne", +"Angeac-Charente", "Angerville-Bailleul", +"Angerville-l'Orcher", "Angerville-la-Campagne", "Angerville-la-Martel", -"Angerville-l'Orcher", "Anglards-de-Saint-Flour", "Anglards-de-Salers", "Anglars-Juillac", "Anglars-Nozac", "Anglars-Saint-Félix", -"Anglesqueville-la-Bras-Long", -"Anglesqueville-l'Esneval", "Angles-sur-l'Anglin", +"Anglesqueville-l'Esneval", +"Anglesqueville-la-Bras-Long", "Anglure-sous-Dun", "Angluzelles-et-Courcelles", "Angoustrine-Villeneuve-des-Escaldes", @@ -641,85 +400,44 @@ FR_BASE_EXCEPTIONS = [ "Angoville-en-Saire", "Angoville-sur-Ay", "Anguilcourt-le-Sart", -"anguille-spaghetti", "Angviller-lès-Bisping", "Anhalt-Bitterfeld", -"animal-garou", -"animalier-soigneur", -"animaux-garous", "Anizy-le-Château", "Annaberg-Buchholz", "Annay-la-Côte", "Annay-sur-Serein", "Anne-Charlotte", -"Annecy-le-Vieux", -"année-homme", -"année-lumière", -"années-homme", -"années-hommes", -"années-lumière", "Anne-Laure", "Anne-Marie", "Anne-Sophie", +"Annecy-le-Vieux", "Annesse-et-Beaulieu", "Annet-sur-Marne", "Anneville-Ambourville", "Anneville-en-Saire", -"Annéville-la-Prairie", "Anneville-sur-Mer", "Anneville-sur-Scie", "Annevoie-Rouillon", "Annoisin-Chatelans", "Annouville-Vilmesnil", -"ano-génital", -"ano-génitale", -"ano-génitales", -"ano-génitaux", +"Annéville-la-Prairie", "Ansac-sur-Vienne", -"ansbach-triesdorfer", +"Anse-Bertrand", "Anse-aux-Fraisois", "Anse-aux-Fraisoise", -"Anse-Bertrand", -"ante-bois", -"anté-diluvien", -"anté-hypophyse", -"anté-hypophyses", -"ante-meridiem", -"ante-meridiems", -"ante-mortem", -"ante-mortems", -"antenne-relais", -"antennes-radar", -"antennes-relais", -"anté-pénultième", -"anté-pénultièmes", -"anté-prédécesseur", -"anté-prédécesseurs", "Antey-Saint-André", "Antezant-la-Chapelle", "Antheuil-Portes", -"anthropo-gammamétrie", -"anthropo-gammamétries", -"anthropo-toponyme", -"anthropo-toponymes", -"anthropo-zoomorphe", -"anthropo-zoomorphes", "Anthy-sur-Léman", "Antichan-de-Frontignes", "Anticostien-Minganien", "Antigny-la-Ville", "Antigua-et-Barbuda", -"antiguais-barbudien", "Antiguais-Barbudien", -"antiguais-barbudiens", "Antiguais-Barbudiens", -"antiguaise-barbudienne", -"Antiguaise-Barbudienne", -"antiguaises-barbudiennes", -"Antiguaises-Barbudiennes", -"antiguais-et-barbudien", "Antiguais-et-Barbudien", -"antilope-chevreuil", +"Antiguaise-Barbudienne", +"Antiguaises-Barbudiennes", "Antogny-le-Tillac", "Antoine-Labellois", "Antonne-et-Trigonant", @@ -728,78 +446,33 @@ FR_BASE_EXCEPTIONS = [ "Anzat-le-Luguet", "Anzin-Saint-Aubin", "Anzy-le-Duc", -"A-OF", +"Anéran-Camors", "Aouste-sur-Sye", "Apenburg-Winterfeld", -"apico-alvéolaire", -"apico-dental", -"appartements-témoins", -"appartement-témoin", -"appel-contre-appel", -"appels-contre-appels", "Appelterre-Eichem", "Appenai-sous-Bellême", "Appeville-Annebault", -"apprentie-sorcière", -"apprenties-sorcières", -"apprenti-sorcellerie", -"apprenti-sorcelleries", -"apprenti-sorcier", -"apprentis-sorciers", -"appui-bras", -"appuie-main", -"appuie-mains", -"appuie-tête", -"appuie-têtes", -"appui-livres", -"appui-main", -"appui-mains", -"appui-pied", -"appui-pieds", -"appui-pot", -"appui-pots", -"appuis-main", -"appuis-pot", -"appuis-tête", -"appui-tête", -"appui-têtes", "Apremont-la-Forêt", "Apremont-sur-Allier", -"aquae-sextien", "Aquae-Sextien", -"aquae-sextienne", "Aquae-Sextienne", -"aquae-sextiennes", "Aquae-Sextiennes", -"aquae-sextiens", "Aquae-Sextiens", -"aqua-tinta", -"aqua-toffana", -"aquila-alba", "Aquitaine-Limousin-Poitou-Charentes", -"Arâches-la-Frasse", -"araignée-crabe", -"araignée-loup", -"araignées-crabes", -"araignées-loups", -"aralo-caspien", -"aralo-caspienne", "Arandon-Passins", "Arbedo-Castione", -"Arbérats-Sillègue", "Arbigny-sous-Varennes", "Arblade-le-Bas", "Arblade-le-Haut", "Arbonne-la-Forêt", "Arbouet-Sussaute", -"arbre-à-la-fièvre", -"arbre-de-Moïse", -"arbres-de-Moïse", -"arbres-refuges", -"arcado-chypriote", -"arcado-chypriotes", -"arcado-cypriote", -"arcado-cypriotes", +"Arbérats-Sillègue", +"Arc-en-Barrois", +"Arc-et-Senans", +"Arc-lès-Gray", +"Arc-sous-Cicon", +"Arc-sous-Montenot", +"Arc-sur-Tille", "Arces-Dilo", "Arcis-le-Ponsart", "Arcis-sur-Aube", @@ -810,134 +483,55 @@ FR_BASE_EXCEPTIONS = [ "Arcy-Sainte-Restitue", "Arcy-sur-Cure", "Ardenay-sur-Mérize", -"ardennite-(As)", -"ardennite-(As)s", -"Ardeuil-et-Montfauxelles", "Ardeuil-Montfauxelles", -"ardi-gasna", +"Ardeuil-et-Montfauxelles", "Arelaune-en-Seine", "Arfeuille-Châtain", "Argelès-Bagnères", "Argelès-Gazost", "Argelès-sur-Mer", "Argens-Minervois", +"Argent-sur-Sauldre", "Argentat-sur-Dordogne", "Argenteuil-sur-Armançon", "Argentière-la-Bessée", -"argentite-β", -"argentite-βs", -"argent-métal", -"argento-analcime", -"argento-analcimes", "Argenton-Château", +"Argenton-Notre-Dame", "Argenton-l'Eglise", "Argenton-l'Église", "Argenton-les-Vallées", -"Argenton-Notre-Dame", "Argenton-sur-Creuse", -"argento-perrylite", -"argento-perrylites", "Argentré-du-Plessis", -"Argent-sur-Sauldre", -"argilo-calcaire", -"argilo-calcaires", -"argilo-gréseuse", -"argilo-gréseuses", -"argilo-gréseux", -"argilo-loessique", -"argilo-loessiques", -"argilo-siliceuse", -"argilo-siliceuses", -"argilo-siliceux", -"arginine-méthyla", -"arginine-méthylai", -"arginine-méthylaient", -"arginine-méthylais", -"arginine-méthylait", -"arginine-méthylâmes", -"arginine-méthylant", -"arginine-méthylas", -"arginine-méthylasse", -"arginine-méthylassent", -"arginine-méthylasses", -"arginine-méthylassiez", -"arginine-méthylassions", -"arginine-méthylât", -"arginine-méthylâtes", -"arginine-méthyle", -"arginine-méthylé", -"arginine-méthylée", -"arginine-méthylées", -"arginine-méthylent", -"arginine-méthyler", -"arginine-méthylera", -"arginine-méthylerai", -"arginine-méthyleraient", -"arginine-méthylerais", -"arginine-méthylerait", -"arginine-méthyleras", -"arginine-méthylèrent", -"arginine-méthylerez", -"arginine-méthyleriez", -"arginine-méthylerions", -"arginine-méthylerons", -"arginine-méthyleront", -"arginine-méthyles", -"arginine-méthylés", -"arginine-méthylez", -"arginine-méthyliez", -"arginine-méthylions", -"arginine-méthylons", -"arginine-vasopressine", "Argiusta-Moriccio", "Argut-Dessous", "Argut-Dessus", -"ariaco-dompierrois", "Ariaco-Dompierrois", -"ariaco-dompierroise", "Ariaco-Dompierroise", -"ariaco-dompierroises", "Ariaco-Dompierroises", "Aries-Espénan", -"aristo-bourgeoisie", -"aristo-bourgeoisies", -"aristotélico-thomiste", -"aristotélico-thomistes", -"arivey-lingeois", "Arivey-Lingeois", -"arivey-lingeoise", "Arivey-Lingeoise", -"arivey-lingeoises", "Arivey-Lingeoises", "Arles-sur-Tech", "Arleux-en-Gohelle", -"armançon-martinois", "Armançon-Martinois", -"armançon-martinoise", "Armançon-Martinoise", -"armançon-martinoises", "Armançon-Martinoises", "Armbouts-Cappel", -"armbouts-cappellois", "Armbouts-Cappellois", -"armbouts-cappelloise", "Armbouts-Cappelloise", -"armbouts-cappelloises", "Armbouts-Cappelloises", "Armenonville-les-Gâtineaux", "Armentières-en-Brie", "Armentières-sur-Avre", "Armentières-sur-Ourcq", "Armous-et-Cau", -"Arnac-la-Poste", "Arnac-Pompadour", +"Arnac-la-Poste", "Arnac-sur-Dourdou", "Arnaud-Guilhem", -"arnaud-guilhémois", "Arnaud-Guilhémois", -"arnaud-guilhémoise", "Arnaud-Guilhémoise", -"arnaud-guilhémoises", "Arnaud-Guilhémoises", "Arnay-le-Duc", "Arnay-sous-Vitteaux", @@ -953,10 +547,6 @@ FR_BASE_EXCEPTIONS = [ "Arpheuilles-Saint-Priest", "Arques-la-Bataille", "Arquettes-en-Val", -"arrache-clou", -"arrache-clous", -"arrache-pied", -"arrache-sonde", "Arraia-Maeztu", "Arrancy-sur-Crusne", "Arras-en-Lavedan", @@ -968,39 +558,27 @@ FR_BASE_EXCEPTIONS = [ "Arrayou-Lahitte", "Arrens-Marsous", "Arrentès-de-Corcieux", -"arrêt-buffet", -"arrêt-court", -"arrête-boeuf", -"arrête-bœuf", -"arrête-bœufs", -"arrêts-buffet", -"arrêts-courts", "Arricau-Bordes", "Arrien-en-Bethmale", "Arrodets-ez-Angles", "Arromanches-les-Bains", -"Arros-de-Nay", "Arros-d'Oloron", -"arrow-root", -"Arsac-en-Velay", -"Ars-en-Ré", -"ars-laquenexois", +"Arros-de-Nay", "Ars-Laquenexois", -"ars-laquenexoise", "Ars-Laquenexoise", -"ars-laquenexoises", "Ars-Laquenexoises", "Ars-Laquenexy", +"Ars-en-Ré", "Ars-les-Favets", "Ars-sur-Formans", "Ars-sur-Moselle", +"Arsac-en-Velay", "Arsure-Arsurette", +"Art-sur-Meurthe", "Artaise-le-Vivier", "Artalens-Souin", "Artannes-sur-Indre", "Artannes-sur-Thouet", -"artério-sclérose", -"artério-scléroses", "Arthaz-Pont-Notre-Dame", "Arthez-d'Armagnac", "Arthez-d'Asson", @@ -1008,59 +586,15 @@ FR_BASE_EXCEPTIONS = [ "Arthon-en-Retz", "Artignosc-sur-Verdon", "Artigues-près-Bordeaux", -"artisan-créateur", -"artisans-créateurs", -"Art-sur-Meurthe", -"art-thérapie", -"art-thérapies", "Arzacq-Arraziguet", "Arzenc-d'Apcher", "Arzenc-de-Randon", "Arzillières-Neuville", +"Arâches-la-Frasse", "Asasp-Arros", "Asbach-Bäumenheim", "Asbach-Sickenberg", "Aschères-le-Marché", -"a-sexualisa", -"a-sexualisai", -"a-sexualisaient", -"a-sexualisais", -"a-sexualisait", -"a-sexualisâmes", -"a-sexualisant", -"a-sexualisas", -"a-sexualisasse", -"a-sexualisassent", -"a-sexualisasses", -"a-sexualisassiez", -"a-sexualisassions", -"a-sexualisât", -"a-sexualisâtes", -"a-sexualise", -"a-sexualisé", -"a-sexualisée", -"a-sexualisées", -"a-sexualisent", -"a-sexualiser", -"a-sexualiser", -"a-sexualisera", -"a-sexualiserai", -"a-sexualiseraient", -"a-sexualiserais", -"a-sexualiserait", -"a-sexualiseras", -"a-sexualisèrent", -"a-sexualiserez", -"a-sexualiseriez", -"a-sexualiserions", -"a-sexualiserons", -"a-sexualiseront", -"a-sexualises", -"a-sexualisés", -"a-sexualisez", -"a-sexualisiez", -"a-sexualisions", -"a-sexualisons", "Asnans-Beauvoisin", "Asnières-en-Bessin", "Asnières-en-Montagne", @@ -1074,70 +608,32 @@ FR_BASE_EXCEPTIONS = [ "Asnières-sur-Saône", "Asnières-sur-Seine", "Asnières-sur-Vègre", +"Aspach-Michelbach", "Aspach-le-Bas", "Aspach-le-Haut", -"Aspach-Michelbach", "Aspin-Aure", "Aspin-en-Lavedan", "Aspres-lès-Corps", "Aspres-sur-Buëch", "Aspret-Sarrat", -"assa-foetida", "Assais-les-Jumeaux", -"Assé-le-Bérenger", -"Assé-le-Boisne", -"Assé-le-Riboul", -"assemble-nuages", -"assiette-à-beurre", -"assis-debout", "Assis-sur-Serre", -"assurance-chômage", -"assurance-chômages", -"assurance-emploi", -"assurances-chômage", -"assurances-vie", -"assurance-vie", -"assyro-chaldéen", "Assyro-Chaldéen", +"Assé-le-Boisne", +"Assé-le-Bérenger", +"Assé-le-Riboul", "Aste-Béon", "Aston-Jonction", -"astronome-astrologue", -"astronomes-astrologues", -"astur-léonais", -"ataxie-télangiectasie", -"Athée-sur-Cher", "Athesans-Etroitefontaine", "Athesans-Étroitefontaine", "Athies-sous-Laon", -"Athis-de-l'Orne", "Athis-Mons", +"Athis-Val de Rouvre", +"Athis-de-l'Orne", "Athos-Aspis", -"attache-bossette", -"attache-bossettes", -"attaché-case", -"attaché-cases", -"attache-doudou", -"attache-doudous", -"attachés-cases", +"Athée-sur-Cher", "Attenrode-Wever", -"attentats-suicides", -"attentat-suicide", "Attignat-Oncin", -"atto-ohm", -"atto-ohms", -"attrape-couillon", -"attrape-couillons", -"attrape-minette", -"attrape-minettes", -"attrape-minon", -"attrape-minons", -"attrape-mouche", -"attrape-mouches", -"attrape-nigaud", -"attrape-nigauds", -"attrape-rêves", -"attrape-tout", -"attrape-vilain", "Aubenas-les-Alpes", "Aubencheul-au-Bac", "Aubencheul-aux-Bois", @@ -1145,19 +641,16 @@ FR_BASE_EXCEPTIONS = [ "Aubepierre-sur-Aube", "Auberives-en-Royans", "Auberives-sur-Varèze", +"Aubermesnil-Beaumais", "Aubermesnil-aux-Erables", "Aubermesnil-aux-Érables", -"Aubermesnil-Beaumais", "Aubert-Gallionnais", "Auberville-la-Campagne", "Auberville-la-Manuel", "Auberville-la-Renault", "Aubeterre-sur-Dronne", -"aube-vigne", "Aubie-et-Espessas", -"Aubigné-Briand", -"Aubigné-Racan", -"Aubigné-sur-Layon", +"Aubigny-Les Clouzeaux", "Aubigny-au-Bac", "Aubigny-aux-Kaisnes", "Aubigny-en-Artois", @@ -1169,6 +662,9 @@ FR_BASE_EXCEPTIONS = [ "Aubigny-lès-Sombernon", "Aubigny-sur-Badin", "Aubigny-sur-Nère", +"Aubigné-Briand", +"Aubigné-Racan", +"Aubigné-sur-Layon", "Aubin-Saint-Vaast", "Auboncourt-Vauzelles", "Aubry-du-Hainaut", @@ -1180,44 +676,25 @@ FR_BASE_EXCEPTIONS = [ "Auchay-sur-Vendée", "Auchy-au-Bois", "Auchy-la-Montagne", -"Auchy-lès-Hesdin", "Auchy-les-Mines", "Auchy-lez-Orchies", -"au-deçà", -"au-dedans", -"au-dehors", -"au-delà", -"au-delàs", +"Auchy-lès-Hesdin", "Aude-Line", -"Audenhove-Sainte-Marie", "Audenhove-Saint-Géry", -"au-dessous", -"au-dessus", -"au-devant", -"audio-numérique", -"audio-numériques", -"audio-prothésiste", -"audio-prothésistes", -"audio-visuel", -"audio-visuelle", -"audio-visuelles", -"audio-visuels", +"Audenhove-Sainte-Marie", "Audouville-la-Hubert", "Audun-le-Roman", "Audun-le-Tiche", "Auffreville-Brasseuil", "Auffrique-et-Nogent", +"Auge-Saint-Médard", "Auger-Saint-Vincent", "Augers-en-Brie", "Augerville-la-Rivière", -"Auge-Saint-Médard", "Augy-sur-Aubois", "Aujan-Mournède", -"aujourd'hui", "Aulhat-Flat", "Aulhat-Saint-Privat", -"aulnaie-frênaie", -"aulnaies-frênaies", "Aulnay-aux-Planches", "Aulnay-l'Aître", "Aulnay-la-Rivière", @@ -1229,17 +706,15 @@ FR_BASE_EXCEPTIONS = [ "Aulnois-sous-Laon", "Aulnois-sous-Vertuzey", "Aulnois-sur-Seille", -"Aulnoye-Aymeries", "Aulnoy-lez-Valenciennes", "Aulnoy-sur-Aube", -"au-lof", -"auloi-jumeaux", +"Aulnoye-Aymeries", "Aulus-les-Bains", "Aulx-lès-Cromary", -"Auménancourt-le-Petit", "Aumeville-Lestre", "Aumont-Aubrac", "Aumont-en-Halatte", +"Auménancourt-le-Petit", "Aunac-sur-Charente", "Aunay-en-Bazois", "Aunay-les-Bois", @@ -1251,78 +726,52 @@ FR_BASE_EXCEPTIONS = [ "Aunou-sur-Orne", "Aurec-sur-Loire", "Aurelle-Verlac", +"Auriac-Lagast", "Auriac-de-Bourzac", "Auriac-du-Périgord", -"Auriac-Lagast", "Auriac-l'Eglise", "Auriac-l'Église", "Auriac-sur-Dropt", "Auriac-sur-Vendinelle", "Auribeau-sur-Siagne", -"auriculo-ventriculaire", -"auriculo-ventriculaires", "Aurions-Idernes", -"aurum-musivum", "Aussac-Vadalle", -"aussi-tost", -"aussi-tôt", "Australie-Méridionale", "Australie-Occidentale", -"australo-américain", -"austro-asiatique", -"austro-asiatiques", -"austro-hongrois", "Austro-Hongrois", -"austro-hongroise", "Austro-Hongroise", -"austro-hongroises", "Austro-Hongroises", -"austro-occidental", -"austro-occidentale", -"austro-occidentales", -"austro-occidentaux", "Autechaux-Roide", -"auteur-compositeur", -"auteure-compositrice", -"auteures-compositrices", -"auteurs-compositeurs", "Autevielle-Saint-Martin-Bideren", "Autheuil-Authouillet", "Autheuil-en-Valois", "Authieux-Ratiéville", -"Authon-du-Perche", "Authon-Ebéon", -"Authon-Ébéon", +"Authon-du-Perche", "Authon-la-Plaine", +"Authon-Ébéon", "Autigny-la-Tour", "Autigny-le-Grand", "Autigny-le-Petit", -"autos-caravanes", -"autos-mitrailleuses", -"autos-scooters", -"autos-tamponnantes", -"autos-tamponneuses", -"au-tour", -"Autrecourt-et-Pourron", -"Autrécourt-sur-Aire", +"Autrans-Méaudre en Vercors", "Autre-Église", -"autre-églisois", "Autre-Églisois", "Autre-Églisoise", -"autre-littérature", -"Autréville-Saint-Lambert", -"Autreville-sur-la-Renne", +"Autrecourt-et-Pourron", "Autreville-sur-Moselle", +"Autreville-sur-la-Renne", +"Autrey-le-Vay", "Autrey-lès-Cerre", "Autrey-lès-Gray", -"Autrey-le-Vay", "Autriche-Hongrie", "Autruy-sur-Juine", "Autry-Issards", "Autry-le-Châtel", +"Autrécourt-sur-Aire", +"Autréville-Saint-Lambert", "Auvergne-Rhône-Alpes", -"Auvers-le-Hamon", "Auvers-Saint-Georges", +"Auvers-le-Hamon", "Auvers-sous-Montfaucon", "Auvers-sur-Oise", "Auvet-et-la-Chapelotte", @@ -1338,90 +787,63 @@ FR_BASE_EXCEPTIONS = [ "Auxon-Dessus", "Auzat-la-Combelle", "Auzat-sur-Allier", -"Auzéville-en-Argonne", "Auzeville-Tolosane", "Auzouer-en-Touraine", "Auzouville-Auberbosc", "Auzouville-l'Esneval", "Auzouville-sur-Ry", "Auzouville-sur-Saâne", -"Availles-en-Châtellerault", +"Auzéville-en-Argonne", "Availles-Limouzine", +"Availles-Thouarsais", +"Availles-en-Châtellerault", "Availles-sur-Chizé", "Availles-sur-Seiche", -"Availles-Thouarsais", -"avale-tout", -"avale-tout-cru", -"avale-touts", "Avanne-Aveney", -"avants-centres", -"avants-postes", +"Avant-lès-Marcilly", +"Avant-lès-Ramerupt", "Avaux-la-Ville", "Ave-et-Auffe", -"ave-et-auffois", "Ave-et-Auffois", "Ave-et-Auffoise", "Avenay-Val-d'Or", "Avernas-le-Bauduin", "Avernes-Saint-Gourgon", "Avernes-sous-Exmes", -"averno-méditerranéen", -"averno-méditerranéenne", -"averno-méditerranéennes", -"averno-méditerranéens", -"Avéron-Bergelle", "Avesnes-Chaussoy", "Avesnes-en-Bray", "Avesnes-en-Saosnois", "Avesnes-en-Val", "Avesnes-le-Comte", +"Avesnes-le-Sec", "Avesnes-les-Aubert", "Avesnes-lès-Bapaume", -"Avesnes-le-Sec", "Avesnes-sur-Helpe", -"aveugle-né", -"aveugle-née", -"aveugles-nés", "Avezac-Prat-Lahitte", -"A.-Vict.", -"Avignonet-Lauragais", "Avignon-lès-Saint-Claude", +"Avignonet-Lauragais", "Avillers-Sainte-Croix", "Avilly-Saint-Léonard", -"avion-cargo", -"avions-cargos", "Avirey-Lingey", -"avoir-du-poids", "Avon-la-Pèze", "Avon-les-Roches", "Avrigney-Virey", -"Avrillé-les-Ponceaux", "Avril-sur-Loire", +"Avrillé-les-Ponceaux", +"Avéron-Bergelle", "Awala-Yalimapo", "Ax-les-Thermes", -"axo-missien", "Axo-Missien", -"axo-missienne", "Axo-Missienne", -"axo-missiennes", "Axo-Missiennes", -"axo-missiens", "Axo-Missiens", +"Ay-sur-Moselle", "Ayala-Aiara", -"ayant-cause", -"ayant-droit", -"ayants-cause", -"ayants-droit", "Ayat-sur-Sioule", -"Aÿ-Champagne", -"aye-aye", "Ayer's-Cliffois", -"ayes-ayes", "Ayguatébia-Talau", "Ayguemorte-les-Graves", "Ayros-Arbouix", -"Ay-sur-Moselle", -"ayur-veda", "Ayzac-Ost", "Azannes-et-Soumazannes", "Azanuy-Alins", @@ -1435,247 +857,141 @@ FR_BASE_EXCEPTIONS = [ "Azay-sur-Indre", "Azay-sur-Thouet", "Azilone-Ampaza", -"azinphos-éthyl", -"azinphos-méthyl", "Azy-le-Vif", "Azy-sur-Marne", +"Aínsa-Sobrarbe", +"Aïcirits-Camou-Suhast", +"Aïd-el-Kébir", +"Aïd-el-Séghir", +"Aÿ-Champagne", "B-52", +"B-frame", +"B-spline", +"B-splines", +"BVD-MD", "Baaks-Sweijer", "Baar-Ebenhausen", "Baarle-Nassau", "Baarle-Nassau-Grens", -"baa'thisa", -"baa'thisai", -"baa'thisaient", -"baa'thisais", -"baa'thisait", -"baa'thisâmes", -"baa'thisant", -"baa'thisas", -"baa'thisasse", -"baa'thisassent", -"baa'thisasses", -"baa'thisassiez", -"baa'thisassions", -"baa'thisât", -"baa'thisâtes", -"baa'thise", -"baa'thisé", -"baa'thisée", -"baa'thisées", -"baa'thisent", -"baa'thiser", -"baa'thisera", -"baa'thiserai", -"baa'thiseraient", -"baa'thiserais", -"baa'thiserait", -"baa'thiseras", -"baa'thisèrent", -"baa'thiserez", -"baa'thiseriez", -"baa'thiserions", -"baa'thiserons", -"baa'thiseront", -"baa'thises", -"baa'thisés", -"baa'thisez", -"baa'thisiez", -"baa'thisions", -"baa'thisons", -"b-a-ba", -"b.a.-ba", "Babeau-Bouldoux", -"babil's", -"babine-witsuwit'en", -"baby-beef", -"baby-beefs", -"baby-boom", -"baby-boomer", -"baby-boomers", -"baby-boomeur", -"baby-boomeurs", -"baby-boomeuse", -"baby-boomeuses", -"baby-foot", -"baby-foots", -"baby-sitter", -"baby-sitters", -"baby-sitting", -"baby-sittings", -"bachat-long", -"bachat-longs", -"bachi-bouzouck", -"bachi-bouzoucks", -"bachi-bouzouk", -"bachi-bouzouks", "Bachos-Binos", "Bachte-Maria-Leerne", "Bacouel-sur-Selle", "Bacqueville-en-Caux", +"Bade-Wurtemberg", "Badecon-le-Pin", "Badefols-d'Ans", "Badefols-de-Cadouin", "Badefols-sur-Dordogne", "Baden-Baden", -"Bade-Wurtemberg", "Badménil-aux-Bois", "Badonvilliers-Gérauvilliers", "Baerle-Duc", "Bagat-en-Quercy", -"Bâgé-la-Ville", -"Bâgé-le-Châtel", "Bagnac-sur-Célé", "Bagneaux-sur-Loing", -"Bagnères-de-Bigorre", -"Bagnères-de-Luchon", "Bagneux-la-Fosse", +"Bagnoles de l'Orne Normandie", "Bagnoles-de-l'Orne", "Bagnols-en-Forêt", "Bagnols-les-Bains", "Bagnols-sur-Cèze", +"Bagnères-de-Bigorre", +"Bagnères-de-Luchon", "Baguer-Morvan", "Baguer-Pican", -"bahá'í", -"bahá'íe", -"bahá'íes", -"bahá'ís", -"Bahá'u'lláh", "Bahus-Soubiran", +"Bahá'u'lláh", "Baie-Catherinois", "Baie-Comelien", "Baie-Comellien", "Baie-Comien", "Baie-Comois", -"Baie-des-Sablien", -"Baie-du-Febvre", "Baie-Jolien", "Baie-Mahault", -"baie-mahaultien", "Baie-Mahaultien", -"baie-mahaultienne", "Baie-Mahaultienne", -"baie-mahaultiennes", "Baie-Mahaultiennes", -"baie-mahaultiens", "Baie-Mahaultiens", "Baie-Saint-Paulois", "Baie-Trinitois", +"Baie-des-Sablien", +"Baie-du-Febvre", "Baignes-Sainte-Radegonde", "Baigneux-les-Juifs", "Baigts-de-Béarn", "Bailleau-Armenonville", -"Bailleau-le-Pin", "Bailleau-l'Evêque", "Bailleau-l'Évêque", -"baille-blé", +"Bailleau-le-Pin", "Baillet-en-France", +"Bailleul-Neuville", +"Bailleul-Sir-Berthoult", "Bailleul-aux-Cornailles", "Bailleul-la-Vallée", "Bailleul-le-Soc", "Bailleul-lès-Pernes", -"Bailleul-Neuville", -"Bailleul-Sir-Berthoult", "Bailleul-sur-Thérain", -"Bailly-aux-Forges", "Bailly-Carrois", +"Bailly-Romainvilliers", +"Bailly-aux-Forges", "Bailly-en-Rivière", "Bailly-le-Franc", -"Bailly-Romainvilliers", "Bain-de-Bretagne", -"bain-douche", -"bain-marie", -"bains-douches", "Bains-les-Bains", -"bains-marie", "Bains-sur-Oust", "Bainville-aux-Miroirs", "Bainville-aux-Saules", "Bainville-sur-Madon", -"Bairon-le-Mont-Dieu", "Bairon-Mont-Dieu", -"baise-en-ville", -"baise-main", +"Bairon-le-Mont-Dieu", "Baisy-Thy", "Bakkum-Noord", "Balagny-sur-Thérain", "Balaguier-d'Olt", "Balaguier-sur-Rance", -"balai-brosse", -"balais-brosses", "Balaives-et-Butz", -"Balaruc-les-Bains", "Balaruc-le-Vieux", -"Bâle-Campagne", -"baleine-pilote", -"baleines-pilotes", +"Balaruc-les-Bains", "Balesmes-sur-Marne", -"Bâle-Ville", "Baliracq-Maumusson", -"Ballancourt-sur-Essonne", "Ballan-Miré", -"balle-molle", -"balle-queue", +"Ballancourt-sur-Essonne", "Balleroy-sur-Drôme", -"ballon-panier", +"Ballon-Saint Mars", "Ballon-Saint-Mars", -"ballon-sonde", -"ballons-panier", -"ballons-paniers", -"ballons-sondes", -"ballon-volant", "Ballrechten-Dottingen", -"ball-trap", -"bal-musette", "Balnot-la-Grange", "Balnot-sur-Laignes", -"bals-musette", -"bana-bana", -"bana-banas", -"banana-split", -"banana-splits", -"Banassac-Canilhac", -"bande-annonce", +"Ban-Saint-Martin", +"Ban-Saint-Martinois", +"Ban-Saint-Martinoise", +"Ban-Saint-Martinoises", "Ban-de-Laveline", -"ban-de-lavelinois", "Ban-de-Lavelinois", -"ban-de-lavelinoise", "Ban-de-Lavelinoise", -"ban-de-lavelinoises", "Ban-de-Lavelinoises", -"bandes-annonces", "Ban-de-Sapt", -"bande-son", -"bank-note", -"bank-notes", +"Ban-sur-Meurthe", +"Ban-sur-Meurthe-Clefcy", +"Banassac-Canilhac", "Banneville-la-Campagne", "Banneville-sur-Ajon", "Bannost-Villegagnon", "Banogne-Recouvrance", -"Ban-Saint-Martin", -"ban-saint-martinois", -"Ban-Saint-Martinois", -"ban-saint-martinoise", -"Ban-Saint-Martinoise", -"ban-saint-martinoises", -"Ban-Saint-Martinoises", -"Ban-sur-Meurthe", -"Ban-sur-Meurthe-Clefcy", "Banyuls-dels-Aspres", "Banyuls-sur-Mer", "Baons-le-Comte", "Bapeaume-lès-Rouen", +"Bar-et-Harricourt", +"Bar-le-Duc", +"Bar-lès-Buzancy", +"Bar-sur-Aube", +"Bar-sur-Seine", "Barbazan-Debat", "Barbazan-Dessus", -"barbe-à-papa", "Barbe-Bleue", -"barbe-de-bouc", -"barbe-de-capucin", -"barbe-de-chèvre", -"barbe-de-Jupiter", "Barberey-Saint-Sulpice", -"barbes-de-capucin", -"barbes-de-Jupiter", "Barbey-Seroux", "Barbezieux-Saint-Hilaire", "Barbirey-sur-Ouche", @@ -1683,13 +999,11 @@ FR_BASE_EXCEPTIONS = [ "Barcelonne-du-Gers", "Bard-le-Régulier", "Bard-lès-Epoisses", -"Bard-lès-Époisses", "Bard-lès-Pesmes", +"Bard-lès-Époisses", "Barenton-Bugny", "Barenton-Cel", "Barenton-sur-Serre", -"Barésia-sur-l'Ain", -"Bar-et-Harricourt", "Barger-Compascuum", "Barger-Erfscheidenveen", "Barger-Oosterveen", @@ -1698,16 +1012,6 @@ FR_BASE_EXCEPTIONS = [ "Barisey-au-Plain", "Barisey-la-Côte", "Barisis-aux-Bois", -"barium-adulaire", -"barium-adulaires", -"barium-anorthite", -"barium-anorthites", -"barium-phlogopite", -"barium-phlogopites", -"barium-sanidine", -"barium-sanidines", -"Bar-le-Duc", -"Bar-lès-Buzancy", "Barletta-Andria-Trani", "Barneville-Carteret", "Barneville-la-Bertran", @@ -1716,9 +1020,7 @@ FR_BASE_EXCEPTIONS = [ "Barou-en-Auge", "Barrais-Bussolles", "Barraute-Camu", -"barré-bandé", "Barre-des-Cévennes", -"barrés-bandés", "Barret-de-Lioure", "Barret-le-Bas", "Barret-le-Haut", @@ -1726,95 +1028,36 @@ FR_BASE_EXCEPTIONS = [ "Barriac-les-Bosquets", "Barrow-in-Furness", "Barry-d'Islemade", -"bars-tabacs", -"Bar-sur-Aube", -"Bar-sur-Seine", -"bar-tabac", -"bar-tabacs", "Bartenshagen-Parkentin", "Barvaux-Condroz", "Barville-en-Gâtinais", -"baryton-basse", -"barytons-basses", -"baryum-orthose", -"baryum-orthoses", "Barzy-en-Thiérache", "Barzy-sur-Marne", +"Barésia-sur-l'Ain", +"Bas-Lieu", +"Bas-Mauco", +"Bas-en-Basset", +"Bas-et-Lezat", "Basadingen-Schlattingen", -"basco-béarnaise", -"basco-navarrais", -"base-ball", -"base-balls", -"base-jump", -"base-jumpeur", -"base-jumpeurs", -"base-jumpeuse", -"base-jumpeuses", -"basi-sphénoïdal", -"basket-ball", -"basket-balls", "Baslieux-lès-Fismes", "Baslieux-sous-Châtillon", -"baso-cellulaire", -"baso-cellulaires", -"basque-uruguayen", -"basset-hound", -"bassi-colica", -"bassi-colicas", +"Basse-Goulaine", +"Basse-Ham", +"Basse-Pointe", +"Basse-Rentgen", +"Basse-Terre", +"Basse-sur-le-Rupt", "Bassignac-le-Bas", "Bassignac-le-Haut", "Bassillac-et-Auberoche", "Bassillon-Vauzé", -"bassins-versants", -"bassin-versant", "Bassoles-Aulers", -"bat-à-beurre", -"bat-à-bourre", -"bateau-bus", -"bateau-citerne", -"bateau-dragon", -"bateau-école", -"bateau-feu", -"bateau-lavoir", -"bateau-logement", -"bateau-mère", -"bateau-mouche", -"bateau-phare", -"bateau-usine", -"bateau-vanne", -"bateaux-bus", -"bateaux-citernes", -"bateaux-dragons", -"bateaux-écoles", -"bateaux-feu", -"bateaux-lavoirs", -"bateaux-logements", -"bateaux-mères", -"bateaux-mouches", -"bateaux-phare", -"bateaux-usines", -"bateaux-vanne", -"bat-flanc", -"bat-flancs", "Bathelémont-lès-Bauzemont", "Batignolles-Monceaux", "Batilly-en-Gâtinais", "Batilly-en-Puisaye", -"bat-l'eau", -"bats-à-beurre", -"bats-à-bourre", -"bats-l'eau", -"battant-l'oeil", -"battant-l'œil", -"battants-l'oeil", -"battants-l'œil", -"batte-lessive", -"batte-mare", -"Battenans-les-Mines", "Battenans-Varin", -"batte-plate", -"batte-queue", -"battes-plates", +"Battenans-les-Mines", "Batz-sur-Mer", "Baudinard-sur-Verdon", "Baugé-en-Anjou", @@ -1822,36 +1065,30 @@ FR_BASE_EXCEPTIONS = [ "Baulne-en-Brie", "Baume-les-Dames", "Baume-les-Messieurs", -"baussery-montain", "Baussery-Montain", -"baussery-montaine", "Baussery-Montaine", -"baussery-montaines", "Baussery-Montaines", -"baussery-montains", "Baussery-Montains", +"Bay-sur-Aube", "Bayard-sur-Marne", "Bayenghem-lès-Eperlecques", -"Bayenghem-lès-Éperlecques", "Bayenghem-lès-Seninghem", +"Bayenghem-lès-Éperlecques", "Bayerfeld-Steckweiler", -"bay-ice", -"bay-ices", "Bayon-sur-Gironde", "Bayonville-sur-Mad", -"Bay-sur-Aube", "Bazeilles-sur-Othain", "Bazincourt-sur-Epte", "Bazincourt-sur-Saulx", "Bazoches-au-Houlme", "Bazoches-en-Dunois", -"Bazoches-lès-Bray", "Bazoches-les-Gallerandes", "Bazoches-les-Hautes", +"Bazoches-lès-Bray", "Bazoches-sur-Guyonne", "Bazoches-sur-Hoëne", -"Bazoches-sur-le-Betz", "Bazoches-sur-Vesles", +"Bazoches-sur-le-Betz", "Bazoges-en-Paillers", "Bazoges-en-Pareds", "Bazoilles-et-Ménil", @@ -1861,23 +1098,16 @@ FR_BASE_EXCEPTIONS = [ "Bazouges-sur-le-Loir", "Bazus-Aure", "Bazus-Neste", -"beach-volley", -"beach-volleys", -"beagle-harrier", -"Béard-Géovreissiat", "Beaubec-la-Rosière", +"Beaucamps-Ligny", "Beaucamps-le-Jeune", "Beaucamps-le-Vieux", -"Beaucamps-Ligny", "Beauchamps-sur-Huillard", -"beau-chasseur", "Beauchery-Saint-Martin", "Beaucourt-en-Santerre", "Beaucourt-sur-l'Ancre", "Beaucourt-sur-l'Hallue", -"beau-dabe", "Beauficel-en-Lyons", -"beau-fils", "Beaufort-Blavincourt", "Beaufort-en-Anjou", "Beaufort-en-Argonne", @@ -1885,30 +1115,35 @@ FR_BASE_EXCEPTIONS = [ "Beaufort-en-Vallée", "Beaufort-sur-Gervanne", "Beaufour-Druval", -"beau-frais", -"beau-frère", "Beaugies-sous-Bois", "Beaujeu-Saint-Vallier-Pierrejux-et-Quitteur", -"beaujolais-villages", "Beaulieu-en-Argonne", "Beaulieu-les-Fontaines", "Beaulieu-lès-Loches", "Beaulieu-sous-Bressuire", -"Beaulieu-sous-la-Roche", "Beaulieu-sous-Parthenay", +"Beaulieu-sous-la-Roche", "Beaulieu-sur-Dordogne", "Beaulieu-sur-Layon", "Beaulieu-sur-Loire", "Beaulieu-sur-Mer", "Beaulieu-sur-Oudon", "Beaulieu-sur-Sonnette", -"beau-livre", "Beaulne-et-Chivy", "Beaumerie-Saint-Martin", "Beaumes-de-Venise", "Beaumetz-lès-Aire", "Beaumetz-lès-Cambrai", "Beaumetz-lès-Loges", +"Beaumont-Hague", +"Beaumont-Hamel", +"Beaumont-Louestault", +"Beaumont-Monteux", +"Beaumont-Pied-de-Bœuf", +"Beaumont-Pied-de-Bœuf", +"Beaumont-Saint-Cyr", +"Beaumont-Sardolles", +"Beaumont-Village", "Beaumont-de-Lomagne", "Beaumont-de-Pertuis", "Beaumont-du-Gâtinais", @@ -1922,8 +1157,6 @@ FR_BASE_EXCEPTIONS = [ "Beaumont-en-Diois", "Beaumont-en-Verdunois", "Beaumont-en-Véron", -"Beaumont-Hague", -"Beaumont-Hamel", "Beaumont-la-Chartre", "Beaumont-la-Ferrière", "Beaumont-la-Ronce", @@ -1933,11 +1166,6 @@ FR_BASE_EXCEPTIONS = [ "Beaumont-les-Nonains", "Beaumont-lès-Randan", "Beaumont-lès-Valence", -"Beaumont-Louestault", -"Beaumont-Monteux", -"Beaumont-Pied-de-Bœuf", -"Beaumont-Saint-Cyr", -"Beaumont-Sardolles", "Beaumont-sur-Dême", "Beaumont-sur-Grosne", "Beaumont-sur-Lèze", @@ -1945,7 +1173,6 @@ FR_BASE_EXCEPTIONS = [ "Beaumont-sur-Sarthe", "Beaumont-sur-Vesle", "Beaumont-sur-Vingeanne", -"Beaumont-Village", "Beaumotte-Aubertans", "Beaumotte-lès-Montbozon-et-Aubertans", "Beaumotte-lès-Pin", @@ -1953,188 +1180,79 @@ FR_BASE_EXCEPTIONS = [ "Beaune-la-Rolande", "Beaune-les-Mines", "Beaune-sur-Arzon", -"beau-papa", -"beau-parent", -"beau-partir", -"beau-père", -"beau-petit-fils", "Beaupréau-en-Mauges", "Beaurains-lès-Noyon", "Beauregard-Baret", +"Beauregard-Vendon", "Beauregard-de-Terrasson", "Beauregard-et-Bassac", "Beauregard-l'Evêque", "Beauregard-l'Évêque", -"Beauregard-Vendon", "Beaurepaire-en-Bresse", "Beaurepaire-sur-Sambre", -"beau-revoir", -"beau-semblant", -"Beaussais-sur-Mer", "Beaussais-Vitré", +"Beaussais-sur-Mer", "Beauvais-sur-Matha", "Beauvais-sur-Tescou", "Beauval-en-Caux", +"Beauvoir-Rivière", +"Beauvoir-Wavans", "Beauvoir-de-Marc", "Beauvoir-en-Lyons", "Beauvoir-en-Royans", -"Beauvoir-Rivière", "Beauvoir-sur-Mer", "Beauvoir-sur-Niort", "Beauvoir-sur-Sarce", -"Beauvoir-Wavans", "Beauvois-en-Cambrésis", "Beauvois-en-Vermandois", -"beaux-arts", "Beaux-Arts", -"beaux-dabes", -"beaux-enfants", -"beaux-esprits", -"beaux-fils", -"beaux-frères", -"beaux-oncles", -"beaux-parents", -"beaux-pères", -"beaux-petits-fils", "Beaux-Rivageois", -"bébé-bulle", -"bébé-bus", -"bébé-éprouvette", -"bébé-médicament", -"bébé-nageur", -"bébés-bulles", -"bébés-éprouvette", -"bébés-médicament", -"bébés-nageurs", -"bêche-de-mer", -"bêches-de-mer", +"Bec-de-Mortagne", "Bech-Kleinmacher", -"Bécon-les-Granits", -"Bécordel-Bécourt", -"becque-cornu", -"becques-cornus", -"becs-cornus", -"becs-courbes", -"becs-d'âne", -"becs-d'argent", -"becs-de-cane", -"becs-de-canon", -"becs-de-cigogne", -"becs-de-cire", -"becs-de-corbeau", -"becs-de-crosse", -"becs-de-cygne", -"becs-de-faucon", -"becs-de-grue", -"becs-de-hache", -"becs-de-héron", -"becs-de-lézard", -"becs-de-lièvre", -"becs-de-perroquet", -"becs-de-pigeon", -"becs-de-vautour", -"becs-d'oie", -"becs-durs", -"becs-en-ciseaux", -"becs-en-fourreau", -"becs-ouverts", -"becs-plats", -"becs-pointus", -"becs-ronds", -"becs-tranchants", "Bedburg-Hau", -"Bédeilhac-et-Aynat", -"bedlington-terrier", -"Bédouès-Cocurès", "Beemte-Broekland", "Beffu-et-le-Morthomme", -"bégler-beg", -"béglier-beg", -"Bégrolles-en-Mauges", -"behā'ī", -"Béhasque-Lapiste", -"Behren-lès-Forbach", "Behren-Lübchin", +"Behren-lès-Forbach", "Beiersdorf-Freudenberg", "Beine-Nauroy", "Beintza-Labaien", "Beire-le-Châtel", "Beire-le-Fort", -"bekkō-amé", "Belan-sur-Ource", +"Belbèze-Escoulis", "Belbèze-de-Lauragais", "Belbèze-en-Comminges", "Belbèze-en-Lomagne", -"Belbèze-Escoulis", "Belcastel-et-Buc", -"bel-enfant", -"bel-esprit", -"Bélesta-en-Lauragais", -"bel-étage", -"Belforêt-en-Perche", "Belfort-du-Quercy", "Belfort-sur-Rebenty", -"belgo-hollandais", +"Belforêt-en-Perche", "Belhomert-Guéhouville", "Belin-Béliet", "Belle-Ansois", -"belle-à-voir", -"Bellecombe-en-Bauges", -"Bellecombe-Tarendol", -"belle-dabe", -"belle-dame", -"belle-de-jour", -"belle-de-nuit", -"belle-doche", -"belle-d'onze-heures", -"belle-d'un-jour", "Belle-Eglise", -"Belle-Église", +"Belle-Isle-en-Mer", +"Belle-Isle-en-Terre", "Belle-et-Houllefort", -"belle-étoile", -"belle-famille", -"belle-fille", -"belle-fleur", +"Belle-Église", +"Belle-Île-en-Mer", +"Bellecombe-Tarendol", +"Bellecombe-en-Bauges", +"Bellegarde-Marsal", +"Bellegarde-Poussieu", +"Bellegarde-Sainte-Marie", "Bellegarde-du-Razès", "Bellegarde-en-Diois", "Bellegarde-en-Forez", "Bellegarde-en-Marche", -"Bellegarde-Marsal", -"Bellegarde-Poussieu", -"Bellegarde-Sainte-Marie", "Bellegarde-sur-Valserine", -"Belle-Île-en-Mer", -"Belle-Isle-en-Mer", -"Belle-Isle-en-Terre", -"belle-maman", -"belle-mère", "Bellenod-sous-Origny", "Bellenod-sur-Seine", "Bellenot-sous-Pouilly", -"belle-petite-fille", -"belle-pucelle", "Bellerive-sur-Allier", -"belles-dabes", -"belles-dames", "Belles-Dames", -"belles-de-jour", -"belles-de-nuit", -"belles-doches", -"belles-d'un-jour", -"belles-étoiles", -"belles-familles", -"belles-filles", -"belles-fleurs", "Belles-Forêts", -"belles-lettres", -"belles-mères", -"belle-soeur", -"belle-sœur", -"belles-pucelles", -"belles-soeurs", -"belles-sœurs", -"belles-tantes", -"belle-tante", "Bellevaux-Ligneuville", "Bellevigne-en-Layon", "Belleville-en-Caux", @@ -2149,124 +1267,86 @@ FR_BASE_EXCEPTIONS = [ "Bellou-en-Houlme", "Bellou-le-Trichard", "Bellou-sur-Huisne", +"Belloy-Saint-Léonard", "Belloy-en-France", "Belloy-en-Santerre", -"Belloy-Saint-Léonard", "Belloy-sur-Somme", "Belmont-Bretenoux", +"Belmont-Luthézieu", +"Belmont-Sainte-Foi", +"Belmont-Tramonet", "Belmont-d'Azergues", "Belmont-de-la-Loire", "Belmont-lès-Darney", -"Belmont-Luthézieu", -"Belmont-Sainte-Foi", "Belmont-sur-Buttant", "Belmont-sur-Lausanne", "Belmont-sur-Rance", "Belmont-sur-Vair", "Belmont-sur-Yverdon", -"Belmont-Tramonet", -"bel-oncle", -"bel-outil", "Belrupt-en-Verdunois", -"bels-outils", "Belt-Schutsloot", "Belval-Bois-des-Dames", "Belval-en-Argonne", "Belval-et-Sury", "Belval-sous-Châtillon", -"Belvédère-Campomoro", +"Belvianes-et-Cavirac", "Belvès-de-Castillon", "Belvèze-du-Razès", -"Belvianes-et-Cavirac", +"Belvédère-Campomoro", "Ben-Ahin", -"ben-ahinois", "Ben-Ahinois", "Ben-Ahinoise", "Beneden-Haastrecht", "Beneden-Leeuwen", "Benerville-sur-Mer", -"Bénesse-lès-Dax", -"Bénesse-Maremne", -"Bénévent-et-Charbillac", -"Bénévent-l'Abbaye", "Beney-en-Woëvre", "Bengy-sur-Craon", "Beni-Khiran", -"Béning-lès-Saint-Avold", -"béni-non-non", -"béni-oui-oui", -"Bénivay-Ollon", -"benne-kangourou", "Benque-Dessous-et-Dessus", "Benqué-Molère", -"bensulfuron-méthyle", "Bentayou-Sérée", -"bény-bocain", -"Bény-Bocain", -"bény-bocaine", -"Bény-Bocaine", -"bény-bocaines", -"Bény-Bocaines", -"bény-bocains", -"Bény-Bocains", -"Bény-sur-Mer", -"benzoylprop-éthyl", -"bêque-bois", -"bèque-fleur", -"bèque-fleurs", "Berbérust-Lias", "Bercenay-en-Othe", "Bercenay-le-Hayer", -"Berchem-Sainte-Agathe", "Berchem-Saint-Laurent", -"Berchères-les-Pierres", +"Berchem-Sainte-Agathe", "Berchères-Saint-Germain", +"Berchères-les-Pierres", "Berchères-sur-Vesgre", "Berd'huis", -"berd'huisien", "Berd'huisien", -"berd'huisienne", "Berd'huisienne", -"berd'huisiennes", "Berd'huisiennes", -"berd'huisiens", "Berd'huisiens", "Berendrecht-Zandvliet-Lillo", -"Bérengeville-la-Campagne", +"Berg-op-Zoom", +"Berg-sur-Moselle", +"Bergouey-Viellenave", +"Bergues-sur-Sambre", "Bergères-lès-Vertus", "Bergères-sous-Montmirail", -"Berg-op-Zoom", -"Bergouey-Viellenave", -"Berg-sur-Moselle", -"Bergues-sur-Sambre", -"Bérig-Vintrange", "Berkel-Enschot", "Berkholz-Meyenburg", "Berlencourt-le-Cauroy", -"Berles-au-Bois", "Berles-Monchel", +"Berles-au-Bois", "Berlin-Est", "Berlin-Ouest", "Bernac-Debat", "Bernac-Dessus", "Bernadets-Debat", "Bernadets-Dessus", -"bernard-l'ermite", -"bernard-l'hermite", -"Bernay-en-Champagne", -"Bernay-en-Ponthieu", "Bernay-Saint-Martin", "Bernay-Vilbert", +"Bernay-en-Champagne", +"Bernay-en-Ponthieu", "Berne-Mittelland", "Bernes-sur-Oise", "Berneuil-en-Bray", "Berneuil-sur-Aisne", "Berneval-le-Grand", -"bernico-montois", "Bernico-Montois", -"bernico-montoise", "Bernico-Montoise", -"bernico-montoises", "Bernico-Montoises", "Bernières-d'Ailly", "Bernières-le-Patry", @@ -2276,18 +1356,17 @@ FR_BASE_EXCEPTIONS = [ "Bernkastel-Wittlich", "Bernos-Beaulac", "Bernuy-Zapardiel", -"Berny-en-Santerre", "Berny-Rivière", +"Berny-en-Santerre", "Berny-sur-Noye", -"Bérou-la-Mulotière", "Berre-des-Alpes", -"Berre-les-Alpes", "Berre-l'Etang", "Berre-l'Étang", +"Berre-les-Alpes", "Berrias-et-Casteljau", "Berrogain-Laruns", -"Berry-au-Bac", "Berry-Bouy", +"Berry-au-Bac", "Bersac-sur-Rivalier", "Bersillies-l'Abbaye", "Bertaucourt-Epourdon", @@ -2298,62 +1377,49 @@ FR_BASE_EXCEPTIONS = [ "Bertsdorf-Hörnitz", "Berville-en-Roumois", "Berville-la-Campagne", -"Berviller-en-Moselle", "Berville-sur-Mer", "Berville-sur-Seine", +"Berviller-en-Moselle", +"Berzy-le-Sec", "Berzé-la-Ville", "Berzé-le-Châtel", -"Berzy-le-Sec", "Besny-et-Loizy", "Bessais-le-Fromental", "Bessay-sur-Allier", -"Bessède-de-Sault", "Besse-et-Saint-Anastaise", -"Bessé-sur-Braye", "Besse-sur-Issole", "Bessey-en-Chaume", "Bessey-la-Cour", "Bessey-lès-Cîteaux", "Bessines-sur-Gartempe", "Bessy-sur-Cure", -"béta-cyfluthrine", -"béta-gal", +"Bessède-de-Sault", +"Bessé-sur-Braye", "Betbezer-d'Armagnac", "Betcave-Aguin", -"Béthancourt-en-Valois", -"Béthancourt-en-Vaux", -"Béthemont-la-Forêt", -"Béthencourt-sur-Mer", -"Béthencourt-sur-Somme", -"Béthisy-Saint-Martin", -"Béthisy-Saint-Pierre", "Beton-Bazoches", -"Betoncourt-lès-Brotte", -"Betoncourt-les-Ménétriers", "Betoncourt-Saint-Pancras", +"Betoncourt-les-Ménétriers", +"Betoncourt-lès-Brotte", "Betoncourt-sur-Mance", "Betpouey-Barèges", "Bettancourt-la-Ferrée", "Bettancourt-la-Longue", "Bettange-sur-Mess", "Bettegney-Saint-Brice", -"bette-marine", "Bettencourt-Rivière", "Bettencourt-Saint-Ouen", -"bettes-marines", "Betting-lès-Saint-Avold", "Betton-Bettonet", "Bettoncourt-le-Haut", "Betz-le-Château", "Beulotte-Saint-Laurent", -"beun'aise", "Beura-Cardezza", "Beurey-Bauguay", "Beurey-sur-Saulx", -"beurre-frais", "Beuvron-en-Auge", -"Beuvry-la-Forêt", "Beuvry-Nord", +"Beuvry-la-Forêt", "Beuzec-Cap-Sizun", "Beuzec-Conq", "Beuzeville-au-Plain", @@ -2361,33 +1427,22 @@ FR_BASE_EXCEPTIONS = [ "Beuzeville-la-Grenier", "Beuzeville-la-Guérard", "Beveland-Nord", -"Béville-le-Comte", "Bexhill-on-Sea", +"Bey-sur-Seille", "Beychac-et-Caillau", "Beynac-et-Cazenac", "Beyne-Heusay", -"Beyrède-Jumet", "Beyren-lès-Sierck", "Beyrie-en-Béarn", "Beyrie-sur-Joyeuse", -"Bey-sur-Seille", +"Beyrède-Jumet", +"Bez-et-Esparon", "Bezange-la-Grande", "Bezange-la-Petite", -"Bézaudun-les-Alpes", -"Bézaudun-sur-Bîne", -"Bez-et-Esparon", "Bezins-Garraux", -"Bézues-Bajon", -"Bézu-la-Forêt", -"Bézu-le-Guéry", -"Bézu-Saint-Eloi", -"Bézu-Saint-Éloi", -"Bézu-Saint-Germain", -"B-frame", "Biache-Saint-Vaast", "Bians-les-Usiers", "Biars-sur-Cère", -"biche-cochon", "Bichelsee-Balterswil", "Bidania-Goiatz", "Bief-des-Maisons", @@ -2396,81 +1451,30 @@ FR_BASE_EXCEPTIONS = [ "Biel-Benken", "Biencourt-sur-Orge", "Bienne-lez-Happart", -"biens-fonds", "Bienville-la-Petite", "Bienvillers-au-Bois", -"bière-pong", "Bierre-lès-Semur", "Bierry-les-Belles-Fontaines", "Biesme-sous-Thuin", "Biest-Houtakker", "Bietigheim-Bissingen", -"Biéville-Beuville", -"Biéville-en-Auge", -"Biéville-Quétiéville", -"Biéville-sur-Orne", "Big-bang", -"big-endian", "Bignicourt-sur-Marne", "Bignicourt-sur-Saulx", -"bil-ka", -"bil-kas", "Billens-Hennens", "Billigheim-Ingenheim", "Billy-Berclau", "Billy-Chevannes", +"Billy-Montigny", "Billy-le-Grand", "Billy-lès-Chanceaux", -"Billy-Montigny", -"Billy-sous-les-Côtes", "Billy-sous-Mangiennes", +"Billy-sous-les-Côtes", "Billy-sur-Aisne", "Billy-sur-Oisy", "Billy-sur-Ourcq", -"bin-bin", -"bin-bins", -"binge-watcha", -"binge-watchai", -"binge-watchaient", -"binge-watchais", -"binge-watchait", -"binge-watchâmes", -"binge-watchant", -"binge-watchas", -"binge-watchasse", -"binge-watchassent", -"binge-watchasses", -"binge-watchassiez", -"binge-watchassions", -"binge-watchât", -"binge-watchâtes", -"binge-watche", -"binge-watché", -"binge-watchée", -"binge-watchées", -"binge-watchent", -"binge-watcher", -"binge-watchera", -"binge-watcherai", -"binge-watcheraient", -"binge-watcherais", -"binge-watcherait", -"binge-watcheras", -"binge-watchèrent", -"binge-watcherez", -"binge-watcheriez", -"binge-watcherions", -"binge-watcherons", -"binge-watcheront", -"binge-watches", -"binge-watchés", -"binge-watchez", -"binge-watchiez", -"binge-watchions", -"binge-watchons", "Binic-Étables-sur-Mer", "Binnen-Moerdijk", -"bin's", "Binson-et-Orquigny", "Bioley-Magnoux", "Bioley-Orjulaz", @@ -2479,7 +1483,6 @@ FR_BASE_EXCEPTIONS = [ "Birken-Honigsessen", "Bischtroff-sur-Sarre", "Bissao-Guinéen", -"bissau-guinéen", "Bissau-Guinéen", "Bissau-Guinéenne", "Bissau-Guinéennes", @@ -2490,112 +1493,47 @@ FR_BASE_EXCEPTIONS = [ "Bissy-sous-Uxelles", "Bissy-sur-Fley", "Bisten-en-Lorraine", -"bistro-brasserie", -"bistro-brasseries", -"bit-el-mal", "Bithaine-et-le-Val", "Bitschwiller-lès-Thann", "Bitterfeld-Wolfen", -"bitter-pit", "Biurrun-Olcoz", "Biville-la-Baignarde", "Biville-la-Rivière", "Biville-sur-Mer", "Bize-Minervois", -"bla-bla", -"bla-bla-bla", -"black-bass", -"black-blanc-beur", -"black-bottom", -"black-bottoms", +"Biéville-Beuville", +"Biéville-Quétiéville", +"Biéville-en-Auge", +"Biéville-sur-Orne", "Black-Lakien", -"black-out", -"black-outa", -"black-outai", -"black-outaient", -"black-outais", -"black-outait", -"black-outâmes", -"black-outant", -"black-outas", -"black-outasse", -"black-outassent", -"black-outasses", -"black-outassiez", -"black-outassions", -"black-outât", -"black-outâtes", -"black-oute", -"black-outé", -"black-outée", -"black-outées", -"black-outent", -"black-outer", -"black-outera", -"black-outerai", -"black-outeraient", -"black-outerais", -"black-outerait", -"black-outeras", -"black-outèrent", -"black-outerez", -"black-outeriez", -"black-outerions", -"black-outerons", -"black-outeront", -"black-outes", -"black-outés", -"black-outez", -"black-outiez", -"black-outions", -"black-outons", -"black-outs", -"black-rot", "Blagny-sur-Vingeanne", "Blaincourt-lès-Précy", "Blaincourt-sur-Aube", "Blainville-Crevon", -"Blainville-sur-l'Eau", "Blainville-sur-Mer", "Blainville-sur-Orne", +"Blainville-sur-l'Eau", "Blaise-sous-Arzillières", "Blaise-sous-Hauteville", "Blaison-Gohier", "Blaison-Saint-Sulpice", "Blaisy-Bas", "Blaisy-Haut", -"blanche-coiffe", "Blanche-Eglise", +"Blanche-Neige", "Blanche-Église", "Blanchefosse-et-Bay", -"Blanche-Neige", -"blanche-queue", -"blanche-raie", -"blanches-coiffes", -"blancs-becs", -"blancs-bocs", -"blancs-bois", -"blancs-de-baleine", -"blancs-d'Espagne", -"blancs-en-bourre", -"blancs-estocs", -"blancs-étocs", -"blancs-mangers", -"blancs-manteaux", -"blancs-raisins", -"blancs-seings", -"blancs-signés", "Blandouet-Saint-Jean", "Blangerval-Blangermont", +"Blangy-Tronville", "Blangy-le-Château", "Blangy-sous-Poix", "Blangy-sur-Bresle", "Blangy-sur-Ternoise", -"Blangy-Tronville", "Blankenfelde-Mahlow", "Blanquefort-sur-Briolance", -"Blanzac-lès-Matha", "Blanzac-Porcheresse", +"Blanzac-lès-Matha", "Blanzaguet-Saint-Cybard", "Blanzay-sur-Boutonne", "Blanzy-la-Salonnaise", @@ -2604,114 +1542,99 @@ FR_BASE_EXCEPTIONS = [ "Blaye-et-Sainte-Luce", "Blaye-les-Mines", "Bleigny-le-Carreau", -"Blénod-lès-Pont-à-Mousson", -"Blénod-lès-Toul", -"bleu-bite", -"bleu-manteau", -"bleu-merle", "Bleury-Saint-Symphorien", -"bleus-manteaux", "Bleyen-Genschmar", "Blies-Ebersing", -"Blies-Ébersing", -"blies-ebersingeois", "Blies-Ebersingeois", -"blies-ébersingeois", -"Blies-Ébersingeois", -"blies-ebersingeoise", "Blies-Ebersingeoise", -"blies-ébersingeoise", -"Blies-Ébersingeoise", -"blies-ebersingeoises", "Blies-Ebersingeoises", -"blies-ébersingeoises", -"Blies-Ébersingeoises", "Blies-Guersviller", +"Blies-Ébersing", +"Blies-Ébersingeois", +"Blies-Ébersingeoise", +"Blies-Ébersingeoises", "Bligny-en-Othe", -"Bligny-lès-Beaune", "Bligny-le-Sec", +"Bligny-lès-Beaune", "Bligny-sous-Beaune", "Bligny-sur-Ouche", -"bling-bling", -"bling-blings", "Blis-et-Born", -"blis-et-bornois", "Blis-et-Bornois", -"blis-et-bornoise", "Blis-et-Bornoise", -"blis-et-bornoises", "Blis-et-Bornoises", -"bloc-cylindres", -"bloc-eau", -"bloc-film", -"bloc-films", -"block-système", -"bloc-moteur", -"bloc-moteurs", -"bloc-note", -"bloc-notes", -"blocs-eau", -"blocs-films", -"blocs-notes", "Blois-sur-Seille", "Blonville-sur-Mer", "Blosseville-Bonsecours", "Blot-l'Eglise", "Blot-l'Église", "Blousson-Sérian", -"blue-jean", -"blue-jeans", -"blue-lias", -"blu-ray", -"boat-people", -"bobby-soxer", -"bobby-soxers", +"Blénod-lès-Pont-à-Mousson", +"Blénod-lès-Toul", "Bobenheim-Roxheim", "Bobo-Dioulasso", "Bodeghem-Saint-Martin", "Bodegraven-Reeuwijk", "Bodenrode-Westhausen", "Bodman-Ludwigshafen", -"body-building", "Boeil-Bezing", -"Boën-sur-Lignon", -"Boëssé-le-Sec", -"boeuf-carotte", -"bœuf-carotte", -"bœuf-carottes", -"bœuf-garou", -"Bœurs-en-Othe", "Boevange-sur-Attert", "Bogis-Bossey", "Bogny-lès-Murtin", "Bogny-sur-Meuse", "Bohain-en-Vermandois", "Bohas-Meyriat-Rignat", -"Böhl-Iggelheim", "Boigny-sur-Bionne", "Boinville-en-Mantois", "Boinville-en-Woëvre", "Boinville-le-Gaillard", "Boiry-Becquerelle", "Boiry-Notre-Dame", -"Boiry-Sainte-Rictrude", "Boiry-Saint-Martin", -"Boisleux-au-Mont", +"Boiry-Sainte-Rictrude", +"Bois-Anzeray", +"Bois-Arnault", +"Bois-Bernard", +"Bois-Colombes", +"Bois-Grenier", +"Bois-Guilbert", +"Bois-Guillaume", +"Bois-Herpin", +"Bois-Himont", +"Bois-Héroult", +"Bois-Jérôme-Saint-Ouen", +"Bois-Normand-près-Lyre", +"Bois-Sainte-Marie", +"Bois-d'Amont", +"Bois-d'Arcy", +"Bois-d'Ennebourg", +"Bois-de-Champ", +"Bois-de-Céné", +"Bois-de-Gand", +"Bois-de-la-Pierre", +"Bois-l'Évêque", +"Bois-le-Roi", +"Bois-lès-Pargny", "Boisleux-Saint-Marc", -"Boissei-la-Lande", +"Boisleux-au-Mont", +"Boisné-La Tude", "Boisse-Penchot", -"Boisset-et-Gaujac", -"Boisset-lès-Montrond", -"Boisset-les-Prévanches", +"Boissei-la-Lande", "Boisset-Saint-Priest", +"Boisset-et-Gaujac", +"Boisset-les-Prévanches", +"Boisset-lès-Montrond", "Boissey-le-Châtel", "Boissise-la-Bertrand", "Boissise-le-Roi", +"Boissy-Fresnoy", +"Boissy-Lamberville", +"Boissy-Maugien", +"Boissy-Maugis", +"Boissy-Mauvoisin", +"Boissy-Saint-Léger", "Boissy-aux-Cailles", "Boissy-en-Drouais", -"Boissy-Fresnoy", "Boissy-l'Aillerie", -"Boissy-Lamberville", "Boissy-la-Rivière", "Boissy-le-Bois", "Boissy-le-Châtel", @@ -2719,287 +1642,147 @@ FR_BASE_EXCEPTIONS = [ "Boissy-le-Repos", "Boissy-le-Sec", "Boissy-lès-Perche", -"boissy-maugien", -"Boissy-Maugien", -"boissy-maugienne", -"boissy-maugiennes", -"boissy-maugiens", -"Boissy-Maugis", -"Boissy-Mauvoisin", -"Boissy-Saint-Léger", "Boissy-sans-Avoir", "Boissy-sous-Saint-Yon", "Boissy-sur-Damville", "Boisville-la-Saint-Père", -"boîtes-à-musique", -"boîtes-à-musiques", -"boit-sans-soif", "Bokholt-Hanredder", -"bolivo-paraguayen", "Bollendorf-Pont", -"bombardiers-torpilleurs", -"bombardier-torpilleur", -"Bonac-Irazein", -"bon-air", -"bon-bec", -"Bonchamp-lès-Laval", -"bon-chrétien", -"Boncourt-le-Bois", -"Boncourt-sur-Meuse", -"bon-creux", -"bon-encontrais", "Bon-Encontrais", -"bon-encontraise", "Bon-Encontraise", -"bon-encontraises", "Bon-Encontraises", "Bon-Encontre", -"bon-fieux", -"bon-fils", -"bon-henri", -"bonheur-du-jour", +"Bon-Secourois", +"Bon-Secours", +"Bonac-Irazein", +"Bonchamp-lès-Laval", +"Boncourt-le-Bois", +"Boncourt-sur-Meuse", "Bonlieu-sur-Roubion", -"bon-mot", "Bonnac-la-Côte", -"bonne-dame", -"bonne-encontre", -"bonne-ente", -"bonne-ententiste", -"bonne-ententistes", -"bonne-femme", -"bonne-grâce", -"bonne-main", -"bonne-maman", -"bonnes-dames", -"bonnes-entes", -"bonnes-femmes", -"bonnes-grâces", -"bonnes-mamans", -"bonnes-vilaines", -"bonnes-voglies", -"bonnet-chinois", -"bonnet-de-prêtre", -"bonnet-rouge", -"bonnets-chinois", -"bonnets-de-prêtres", -"bonnets-verts", -"bonnet-vert", +"Bonneuil-Matours", "Bonneuil-en-France", "Bonneuil-en-Valois", "Bonneuil-les-Eaux", -"Bonneuil-Matours", "Bonneuil-sur-Marne", "Bonneval-en-Diois", "Bonneval-sur-Arc", "Bonnevaux-le-Prieuré", -"Bonnevent-et-Velloreille-lès-Bonnevent", "Bonnevent-Velloreille", -"bonne-vilaine", +"Bonnevent-et-Velloreille-lès-Bonnevent", "Bonneville-Aptot", "Bonneville-et-Saint-Avit-de-Fumadières", "Bonneville-la-Louvet", "Bonneville-sur-Touques", -"bonne-voglie", -"Bonnières-sur-Seine", "Bonningues-lès-Ardres", "Bonningues-lès-Calais", +"Bonnières-sur-Seine", "Bonny-sur-Loire", -"bon-ouvrier", -"bon-ouvriers", -"bon-papa", -"bon-plein", "Bonrepos-Riquet", "Bonrepos-sur-Aussonnelle", -"bons-chrétiens", -"Bon-Secourois", -"Bon-Secours", -"Bons-en-Chablais", -"bons-mots", -"bons-papas", "Bons-Tassilly", -"bon-tour", +"Bons-en-Chablais", "Bonvillers-Mont", -"boogie-woogie", -"boogie-woogies", -"Boô-Silhen", "Bootle-cum-Linacre", +"Bor-et-Bar", "Bora-Bora", "Boran-sur-Oise", "Borcq-sur-Airvault", -"Bordeaux-en-Gâtinais", +"Bord-Saint-Georges", "Bordeaux-Saint-Clair", -"Börde-Hakel", +"Bordeaux-en-Gâtinais", "Bordel's", -"borde-plats", -"Bordères-et-Lamensans", +"Bordes-Uchentein", +"Bordes-de-Rivière", "Bordères-Louron", +"Bordères-et-Lamensans", "Bordères-sur-l'Echez", "Bordères-sur-l'Échez", -"border-terrier", -"Bordes-de-Rivière", -"Bordes-Uchentein", -"bord-opposé", -"Bord-Saint-Georges", -"bore-out", -"bore-outs", "Boresse-et-Martron", -"Bor-et-Bar", "Borgdorf-Seedorf", -"Börgerende-Rethwisch", "Borger-Odoorn", "Bormes-les-Mimosas", "Born-de-Champs", -"borne-couteau", -"borne-fontaine", -"borne-fusible", -"borne-fusibles", -"bornes-couteaux", -"bornes-fontaines", +"Bors (Canton de Baignes-Sainte-Radegonde)", +"Bors (Canton de Montmoreau-Saint-Cybard)", "Bors-de-Baignes", "Bors-de-Montmoreau", "Borstel-Hohenraden", -"Bort-les-Orgues", "Bort-l'Etang", "Bort-l'Étang", +"Bort-les-Orgues", +"Bosc-Bordel", "Bosc-Bénard-Commin", "Bosc-Bénard-Crescy", "Bosc-Bérenger", -"Bosc-Bordel", "Bosc-Edeline", -"Bosc-Édeline", -"bosc-guérardais", -"Bosc-Guérardais", -"bosc-guérardaise", -"Bosc-Guérardaise", -"bosc-guérardaises", -"Bosc-Guérardaises", "Bosc-Guérard-Saint-Adrien", +"Bosc-Guérardais", +"Bosc-Guérardaise", +"Bosc-Guérardaises", "Bosc-Hyons", -"Bosc-le-Hard", "Bosc-Mesnil", "Bosc-Renoult-en-Ouche", "Bosc-Renoult-en-Roumois", -"bosc-renoulthien", "Bosc-Renoulthien", -"bosc-renoulthienne", "Bosc-Renoulthienne", -"bosc-renoulthiennes", "Bosc-Renoulthiennes", -"bosc-renoulthiens", "Bosc-Renoulthiens", "Bosc-Roger-sur-Buchy", +"Bosc-le-Hard", +"Bosc-Édeline", "Bosguérard-de-Marcouville", -"Bösleben-Wüllersleben", "Bosmie-l'Aiguille", "Bosmont-sur-Serre", "Bosmoreau-les-Mines", -"Bosnie-et-Herzégovine", "Bosnie-Herzégovine", -"bosno-serbe", -"bosno-serbes", +"Bosnie-et-Herzégovine", "Bossay-sur-Claise", "Bosseval-et-Briancourt", "Bossus-lès-Rumigny", "Bossut-Gottechain", -"botte-chaussettes", -"bottom-up", "Botz-en-Mauges", "Boubers-lès-Hesmond", "Boubers-sur-Canche", +"Bouc-Bel-Air", "Bouchamps-lès-Craon", "Bouchavesnes-Bergen", -"bouche-à-bouche", -"bouche-en-flûte", -"bouche-nez", -"bouche-pora", -"bouche-porai", -"bouche-poraient", -"bouche-porais", -"bouche-porait", -"bouche-porâmes", -"bouche-porant", -"bouche-poras", -"bouche-porasse", -"bouche-porassent", -"bouche-porasses", -"bouche-porassiez", -"bouche-porassions", -"bouche-porât", -"bouche-porâtes", -"bouche-pore", -"bouche-poré", -"bouche-porée", -"bouche-porées", -"bouche-porent", -"bouche-porer", -"bouche-porera", -"bouche-porerai", -"bouche-poreraient", -"bouche-porerais", -"bouche-porerait", -"bouche-poreras", -"bouche-porèrent", -"bouche-porerez", -"bouche-poreriez", -"bouche-porerions", -"bouche-porerons", -"bouche-poreront", -"bouche-pores", -"bouche-porés", -"bouche-porez", -"bouche-poriez", -"bouche-porions", -"bouche-porons", "Bouches-du-Rhône", -"bouche-trou", -"bouche-trous", "Bouchy-Saint-Genest", "Boucieu-le-Roi", "Boucle-Saint-Blaise", "Boucle-Saint-Denis", "Boucoiran-et-Nozières", -"Bouconville-sur-Madt", "Bouconville-Vauclair", "Bouconville-Vauclerc", +"Bouconville-sur-Madt", "Boudy-de-Beauregard", "Boueilh-Boueilho-Lasque", -"bouffe-curé", -"bouffe-curés", -"bouffe-galette", -"Bougé-Chambalud", "Bouges-le-Château", -"Bougy-lez-Neuville", "Bougy-Villars", +"Bougy-lez-Neuville", +"Bougé-Chambalud", "Bouhans-et-Feurg", "Bouhans-lès-Lure", "Bouhans-lès-Montbozon", -"boui-boui", -"bouig-bouig", "Bouilh-Devant", "Bouilh-Péreuilh", "Bouillancourt-en-Séry", "Bouillancourt-la-Bataille", +"Bouilly-en-Gâtinais", "Bouillé-Courdault", "Bouillé-Loretz", "Bouillé-Ménard", "Bouillé-Saint-Paul", -"bouillon-blanc", -"Bouilly-en-Gâtinais", "Bouin-Plumoison", -"bouis-bouis", "Boujan-sur-Libron", -"Boulay-les-Barres", -"Boulay-les-Ifs", -"boulay-morinois", "Boulay-Morinois", -"boulay-morinoise", "Boulay-Morinoise", -"boulay-morinoises", "Boulay-Morinoises", "Boulay-Moselle", +"Boulay-les-Barres", +"Boulay-les-Ifs", "Boule-d'Amont", -"boule-dogue", -"boules-dogues", "Boulieu-lès-Annonay", "Boullay-les-Troux", "Boulogne-Billancourt", @@ -3009,57 +1792,69 @@ FR_BASE_EXCEPTIONS = [ "Boulogne-sur-Mer", "Boult-aux-Bois", "Boult-sur-Suippe", -"boum-boum", "Bouray-sur-Juine", "Bourbach-le-Bas", "Bourbach-le-Haut", "Bourbon-Lancy", +"Bourbon-Vendée", "Bourbon-l'Archambault", "Bourbonne-les-Bains", -"Bourbon-Vendée", "Bourbourg-Campagne", "Bourcefranc-le-Chapus", "Bourdons-sur-Rognon", "Bouret-sur-Canche", -"bourgeois-bohème", -"bourgeois-bohèmes", -"bourgeoise-bohème", -"bourgeoises-bohèmes", +"Bourg-Achard", +"Bourg-Archambault", +"Bourg-Argental", +"Bourg-Beaudouin", +"Bourg-Blanc", +"Bourg-Bruche", +"Bourg-Charente", +"Bourg-Fidèle", +"Bourg-Lastic", +"Bourg-Madame", +"Bourg-Saint-Andéol", +"Bourg-Saint-Bernard", +"Bourg-Saint-Christophe", +"Bourg-Saint-Maurice", +"Bourg-Sainte-Marie", +"Bourg-d'Oueil", +"Bourg-de-Bigorre", +"Bourg-de-Péage", +"Bourg-de-Sirod", +"Bourg-de-Visa", +"Bourg-des-Comptes", +"Bourg-des-Maisons", +"Bourg-du-Bost", +"Bourg-en-Bresse", +"Bourg-et-Comin", +"Bourg-l'Évêque", +"Bourg-la-Reine", +"Bourg-le-Comte", +"Bourg-le-Roi", +"Bourg-lès-Valence", +"Bourg-sous-Châtelet", "Bourget-en-Huile", +"Bourgneuf-Val-d'Or", "Bourgneuf-en-Mauges", "Bourgneuf-en-Retz", -"Bourgneuf-Val-d'Or", "Bourgogne-Franche-Comté", "Bourgogne-Fresne", "Bourgoin-Jallieu", "Bourgtheroulde-Infreville", -"bourgue-épine", -"bourgues-épines", "Bourguignon-lès-Conflans", -"Bourguignon-lès-la-Charité", "Bourguignon-lès-Morey", +"Bourguignon-lès-la-Charité", "Bourguignon-sous-Coucy", "Bourguignon-sous-Montbavin", "Bournainville-Faverolles", "Bourneville-Sainte-Croix", "Bournoncle-Saint-Pierre", "Bouroum-Bouroum", -"bourre-chrétien", -"bourre-de-Marseille", -"bourre-goule", -"bourre-goules", -"bourre-noix", -"bourre-pif", -"bourre-pifs", -"bourres-de-Marseille", "Bourriot-Bergonce", "Bourron-Marlotte", -"bourse-à-berger", -"bourse-à-pasteur", "Bourseigne-Neuve", "Bourseigne-Vieille", -"bourses-à-berger", -"bourses-à-pasteur", "Boury-en-Vexin", "Bousignies-sur-Roc", "Boussac-Bourg", @@ -3068,67 +1863,26 @@ FR_BASE_EXCEPTIONS = [ "Boussu-en-Fagne", "Boussu-lez-Walcourt", "Boussy-Saint-Antoine", -"bout-avant", -"bout-d'aile", -"bout-d'argent", -"bout-dehors", -"bout-de-l'an", "Bout-de-l'Îlien", -"bout-de-manche", -"bout-de-quièvre", "Bout-du-Pont-de-Larn", -"bout-du-pont-de-l'arnais", "Bout-du-Pont-de-l'Arnais", -"bout-du-pont-de-l'arnaise", "Bout-du-Pont-de-l'Arnaise", -"bout-du-pont-de-l'arnaises", "Bout-du-Pont-de-l'Arnaises", -"boute-à-port", -"boute-charge", -"boute-dehors", -"boute-de-lof", -"boute-en-courroie", -"boute-en-train", -"boute-feu", -"boute-hache", -"boute-hors", "Bouteilles-Saint-Sébastien", -"boute-joie", -"boute-lof", "Boutenac-Touvent", -"boutes-à-port", -"boute-selle", -"boute-selles", -"boute-tout-cuire", "Boutiers-Saint-Trojan", "Boutigny-Prouais", "Boutigny-sur-Essonne", -"bouton-d'or", -"bouton-poussoir", -"bouton-pression", -"boutons-d'or", -"boutons-pression", -"bout-rimé", -"bout-saigneux", -"bouts-avant", -"bouts-d'aile", -"bouts-d'argent", -"bouts-dehors", -"bouts-de-l'an", -"bouts-de-manche", -"bouts-de-quièvre", -"bouts-rimés", -"bouts-saigneux", "Bouvaincourt-sur-Bresle", "Bouvesse-Quirieu", "Bouvignes-sur-Meuse", "Bouvigny-Boyeffles", "Bouvincourt-en-Vermandois", +"Boux-sous-Salmaise", "Bouxières-aux-Bois", "Bouxières-aux-Chênes", "Bouxières-aux-Dames", "Bouxières-sous-Froidmont", -"Boux-sous-Salmaise", "Bouy-Luxembourg", "Bouy-sur-Orvin", "Bouze-lès-Beaune", @@ -3136,176 +1890,109 @@ FR_BASE_EXCEPTIONS = [ "Bouzonville-aux-Bois", "Bouzonville-en-Beauce", "Bouzy-la-Forêt", -"Bovée-sur-Barboure", "Boven-Haastrecht", "Boven-Hardinxveld", "Boven-Leeuwen", "Bovisio-Masciago", -"bow-string", -"bow-strings", -"bow-window", -"bow-windows", -"box-calf", -"boxer-short", -"boxer-shorts", -"box-office", -"box-offices", +"Bovée-sur-Barboure", "Boyeux-Saint-Jérôme", -"boy-scout", -"boy-scouts", +"Boën-sur-Lignon", +"Boëssé-le-Sec", +"Boô-Silhen", +"Brabant-Septentrional", +"Brabant-Wallon", "Brabant-du-Nord", "Brabant-en-Argonne", "Brabant-le-Roi", -"Brabant-Septentrional", "Brabant-sur-Meuse", -"Brabant-Wallon", -"bracelet-montre", -"bracelets-montres", -"brachio-céphalique", -"brachio-céphaliques", -"brachio-radial", "Bragelogne-Beauvoir", "Bragny-en-Charollais", "Bragny-sur-Saône", "Brailly-Cornehotte", +"Brain-sur-Allonnes", +"Brain-sur-Longuenée", +"Brain-sur-Vilaine", +"Brain-sur-l'Authion", "Braine-l'Alleud", "Braine-le-Château", "Braine-le-Comte", "Brains-sur-Gée", "Brains-sur-les-Marches", -"Brain-sur-Allonnes", -"Brain-sur-l'Authion", -"Brain-sur-Longuenée", -"Brain-sur-Vilaine", "Brainville-sur-Meuse", "Braisnes-sur-Aronde", -"branches-ursines", -"branche-ursine", "Brancourt-en-Laonnois", "Brancourt-le-Grand", -"brancs-ursines", -"branc-ursine", -"branc-ursines", -"Brandebourg-sur-la-Havel", "Brande-Hörnerkirchen", -"branle-bas", -"branle-gai", -"branle-long", -"branle-queue", -"branles-bas", -"branles-gais", -"branles-longs", +"Brandebourg-sur-la-Havel", "Branoux-les-Taillades", -"branque-ursine", "Branville-Hague", -"Bras-d'Asse", -"bras-d'assien", -"Bras-d'Assien", -"bras-d'assienne", -"Bras-d'Assienne", -"bras-d'assiennes", -"Bras-d'Assiennes", -"bras-d'assiens", -"Bras-d'Assiens", -"brash-ice", -"brash-ices", "Bras-Panon", -"Brassac-les-Mines", -"brasse-camarade", -"brasse-camarades", +"Bras-d'Asse", +"Bras-d'Assien", +"Bras-d'Assienne", +"Bras-d'Assiennes", +"Bras-d'Assiens", "Bras-sur-Meuse", +"Brassac-les-Mines", "Braud-et-Saint-Louis", "Braunau-am-Inn", -"Braux-le-Châtel", -"Braux-Sainte-Cohière", "Braux-Saint-Remy", +"Braux-Sainte-Cohière", +"Braux-le-Châtel", "Bray-Dunes", -"bray-dunois", "Bray-Dunois", -"bray-dunoise", "Bray-Dunoise", -"bray-dunoises", "Bray-Dunoises", -"Braye-en-Laonnois", -"Braye-en-Thiérache", +"Bray-Saint-Aignan", +"Bray-Saint-Christophe", "Bray-en-Val", -"Braye-sous-Faye", -"Braye-sur-Maulne", "Bray-et-Lû", "Bray-la-Campagne", "Bray-lès-Mareuil", -"Bray-Saint-Aignan", -"Bray-Saint-Christophe", "Bray-sur-Seine", "Bray-sur-Somme", +"Braye-en-Laonnois", +"Braye-en-Thiérache", +"Braye-sous-Faye", +"Braye-sur-Maulne", "Brazey-en-Morvan", "Brazey-en-Plaine", -"brazza-congolais", "Brazza-Congolais", -"Bréal-sous-Montfort", -"Bréal-sous-Vitré", -"Bréau-et-Salagosse", -"brèche-dent", -"brèche-dents", -"Brécy-Brières", -"brécy-brièrois", -"Brécy-Brièrois", -"brécy-brièroise", -"Brécy-Brièroise", -"brécy-brièroises", -"Brécy-Brièroises", -"bredi-breda", -"Brégnier-Cordon", -"Bréhain-la-Ville", -"Bréhan-Loudéac", "Breil-sur-Roya", "Breistroff-la-Grande", "Breitenbach-Haut-Rhin", -"brelic-breloque", -"brelique-breloque", -"Brémontier-Merval", "Brem-sur-Mer", -"Brémur-et-Vaurois", "Bresse-sur-Grosne", "Bressey-sur-Tille", "Bretagne-d'Armagnac", "Bretagne-de-Marsan", "Bretigney-Notre-Dame", -"Brétignolles-le-Moulin", "Bretignolles-sur-Mer", "Bretigny-sur-Morrens", -"Brétigny-sur-Orge", "Bretnig-Hauswalde", "Brette-les-Pins", -"Bretteville-du-Grand-Caux", -"Bretteville-le-Rabet", -"Bretteville-l'Orgueilleuse", "Bretteville-Saint-Laurent", +"Bretteville-du-Grand-Caux", +"Bretteville-l'Orgueilleuse", +"Bretteville-le-Rabet", "Bretteville-sur-Ay", "Bretteville-sur-Dives", "Bretteville-sur-Laize", "Bretteville-sur-Odon", "Breuil-Barret", -"breuil-bernardin", "Breuil-Bernardin", -"breuil-bernardine", "Breuil-Bernardine", -"breuil-bernardines", "Breuil-Bernardines", -"breuil-bernardins", "Breuil-Bernardins", "Breuil-Bois-Robert", "Breuil-Chaussée", +"Breuil-Magné", "Breuil-la-Réorte", "Breuil-le-Sec", -"breuil-le-secquois", "Breuil-le-Secquois", -"breuil-le-secquoise", "Breuil-le-Secquoise", -"breuil-le-secquoises", "Breuil-le-Secquoises", "Breuil-le-Vert", -"Breuil-Magné", "Breuil-sur-Marne", "Breukelen-Nijenrode", "Breukelen-Sint-Pieters", @@ -3314,39 +2001,28 @@ FR_BASE_EXCEPTIONS = [ "Breuvery-sur-Coole", "Breux-Jouy", "Breux-sur-Avre", -"Bréville-les-Monts", -"Bréville-sur-Mer", -"Bréxent-Enocq", -"Bréxent-Énocq", "Brey-et-Maison-du-Bois", "Briancourt-et-Montimont", "Briarres-sur-Essonne", -"bric-à-brac", -"brick-goélette", "Bricquebec-en-Cotentin", "Bricqueville-la-Blouette", "Bricqueville-sur-Mer", "Brides-les-Bains", "Brie-Comte-Robert", -"Brié-et-Angonnes", -"Briel-sur-Barse", -"Brienne-la-Vieille", -"Brienne-le-Château", -"Brienne-sur-Aisne", -"Brienon-sur-Armançon", -"Brières-et-Crécy", -"Brières-les-Scellés", -"Brieskow-Finkenheerd", "Brie-sous-Archiac", "Brie-sous-Barbezieux", "Brie-sous-Chalais", "Brie-sous-Matha", "Brie-sous-Mortagne", +"Briel-sur-Barse", +"Brienne-la-Vieille", +"Brienne-le-Château", +"Brienne-sur-Aisne", +"Brienon-sur-Armançon", +"Brieskow-Finkenheerd", "Brieuil-sur-Chizé", "Brieulles-sur-Bar", "Brieulles-sur-Meuse", -"brigadier-chef", -"brigadiers-chefs", "Brig-Glis", "Brignac-la-Plaine", "Brignano-Frascata", @@ -3354,62 +2030,19 @@ FR_BASE_EXCEPTIONS = [ "Brigue-Glis", "Brigueil-le-Chantre", "Briis-sous-Forges", -"brillat-savarin", -"brillet-pontin", "Brillet-Pontin", -"brillet-pontine", "Brillet-Pontine", -"brillet-pontines", "Brillet-Pontines", -"brillet-pontins", "Brillet-Pontins", "Brillon-en-Barrois", -"brin-d'amour", -"brin-d'estoc", +"Brin-sur-Seille", "Brinon-sur-Beuvron", "Brinon-sur-Sauldre", -"brins-d'amour", -"brins-d'estoc", -"Brin-sur-Seille", "Brion-près-Thouet", "Brion-sur-Ource", "Briosne-lès-Sables", "Brioux-sur-Boutonne", "Briquemesnil-Floxicourt", -"bris-d'huis", -"brise-bise", -"brise-bises", -"brise-burnes", -"brise-cou", -"brise-cous", -"brise-fer", -"brise-fers", -"brise-flots", -"brise-glace", -"brise-glaces", -"brise-image", -"brise-images", -"brise-lame", -"brise-lames", -"brise-lunette", -"brise-mariage", -"brise-motte", -"brise-mottes", -"brise-mur", -"brise-murs", -"brise-os", -"brise-pierre", -"brise-pierres", -"brise-raison", -"brise-raisons", -"brise-roche", -"brise-roches", -"brise-scellé", -"brise-scellés", -"brise-soleil", -"brise-tout", -"brise-vent", -"brise-vents", "Brisgau-Haute-Forêt-Noire", "Brison-Saint-Innocent", "Brissac-Quincé", @@ -3424,160 +2057,109 @@ FR_BASE_EXCEPTIONS = [ "Brives-Charensac", "Brives-sur-Charente", "Brixey-aux-Chanoines", +"Brières-et-Crécy", +"Brières-les-Scellés", +"Brié-et-Angonnes", "Brocourt-en-Argonne", "Brohl-Lützing", "Bromont-Lamothe", -"bromophos-éthyl", -"broncho-pneumonie", -"broncho-pneumonies", -"broncho-pulmonaire", -"broncho-pulmonaires", "Broons-sur-Vilaine", "Brot-Dessous", "Brot-Plamboz", "Brotte-lès-Luxeuil", "Brotte-lès-Ray", -"brou-brou", -"broue-pub", -"broue-pubs", -"brouille-blanche", -"brouille-blanches", +"Brou-sur-Chantereine", "Brousse-le-Château", "Brousses-et-Villaret", -"Broussey-en-Blois", "Broussey-Raulecourt", +"Broussey-en-Blois", "Broussy-le-Grand", "Broussy-le-Petit", -"Brou-sur-Chantereine", -"broute-minou", -"broute-minous", -"Broût-Vernet", -"broût-vernetois", -"Broût-Vernetois", -"broût-vernetoise", -"Broût-Vernetoise", -"broût-vernetoises", -"Broût-Vernetoises", "Brouzet-lès-Alès", "Brouzet-lès-Quissac", "Brovello-Carpugnino", -"brown-nosers", -"brown-out", "Broye-Aubigney-Montseugny", +"Broye-Vully", "Broye-les-Loups-et-Verfontaine", "Broye-lès-Pesmes-Aubigney-Montseugny", -"Broye-Vully", +"Broût-Vernet", +"Broût-Vernetois", +"Broût-Vernetoise", +"Broût-Vernetoises", "Bruay-la-Buissière", "Bruay-sur-l'Escaut", +"Bruc-sur-Aff", "Bruchhausen-Vilsen", "Bruchmühlbach-Miesau", "Bruchweiler-Bärenbach", -"Brücken-Hackpfüffel", -"Bruc-sur-Aff", "Brue-Auriac", "Brueil-en-Vexin", -"Bruère-Allichamps", -"bruesme-d'auffe", -"bruesmes-d'auffe", "Bruges-Capbis-Mifaget", "Brugny-Vaudancourt", -"Bruille-lez-Marchiennes", "Bruille-Saint-Amand", -"brûle-amorce", -"brûle-bout", -"brule-gueule", -"brûle-gueule", -"brule-gueules", -"brûle-gueules", -"brule-maison", -"brûle-maison", -"brule-maisons", -"brûle-maisons", -"brule-parfum", -"brûle-parfum", -"brule-parfums", -"brûle-parfums", -"brûle-pourpoint", -"brûle-queue", -"brûle-tout", -"Brûly-de-Pesche", -"brûly-de-peschois", -"Brûly-de-Peschois", -"Brûly-de-Peschoise", +"Bruille-lez-Marchiennes", "Brunstatt-Didenheim", -"brun-suisse", "Brunvillers-la-Motte", -"brute-bonne", -"brut-ingénu", -"bruts-ingénus", "Bruttig-Fankel", "Bruxelles-ville", "Bruyères-et-Montbérault", "Bruyères-le-Châtel", "Bruyères-sur-Fère", "Bruyères-sur-Oise", +"Bruère-Allichamps", "Bry-sur-Marne", -"B-spline", -"B-splines", +"Bréal-sous-Montfort", +"Bréal-sous-Vitré", +"Bréau-et-Salagosse", +"Brécy-Brières", +"Brécy-Brièrois", +"Brécy-Brièroise", +"Brécy-Brièroises", +"Brégnier-Cordon", +"Bréhain-la-Ville", +"Bréhan-Loudéac", +"Brémontier-Merval", +"Brémur-et-Vaurois", +"Brétignolles-le-Moulin", +"Brétigny-sur-Orge", +"Bréville-les-Monts", +"Bréville-sur-Mer", +"Bréxent-Enocq", +"Bréxent-Énocq", +"Brûly-de-Pesche", +"Brûly-de-Peschois", +"Brûly-de-Peschoise", +"Brücken-Hackpfüffel", "Buais-Les-Monts", -"buccin-marin", -"buccins-marins", -"bucco-dentaire", -"bucco-dentaires", -"bucco-génital", -"bucco-génitale", -"bucco-génitales", -"bucco-génitaux", -"bucco-labial", -"bucco-pharyngé", -"bucco-pharyngée", -"bucco-pharyngées", -"bucco-pharyngés", "Bucey-en-Othe", "Bucey-lès-Gy", "Bucey-lès-Traves", -"buck-béan", -"buck-béans", +"Bucy-Saint-Liphard", "Bucy-le-Long", "Bucy-le-Roi", "Bucy-lès-Cerny", "Bucy-lès-Pierrepont", -"Bucy-Saint-Liphard", "Budel-Dorplein", "Budel-Schoot", "Bueil-en-Touraine", -"buenos-airien", "Buenos-Airien", "Buenos-Ayres", -"buen-retiro", "Buhl-Lorraine", +"Buigny-Saint-Maclou", "Buigny-l'Abbé", "Buigny-lès-Gamaches", -"Buigny-Saint-Maclou", -"Buire-au-Bois", "Buire-Courcelles", +"Buire-au-Bois", "Buire-le-Sec", "Buire-sur-l'Ancre", -"Buis-les-Baronnies", -"buis-prévenchais", "Buis-Prévenchais", -"buis-prévenchaise", "Buis-Prévenchaise", -"buis-prévenchaises", "Buis-Prévenchaises", -"buisson-ardent", -"buissons-ardents", +"Buis-les-Baronnies", "Buis-sur-Damville", "Bulat-Pestivien", -"bull-dogs", -"bull-mastiff", -"bull-terrier", -"bull-terriers", "Bully-les-Mines", -"bungee-jumping", -"bungy-jumping", "Buno-Bonnevaux", -"bureau-chef", "Bure-les-Templiers", "Bures-en-Bray", "Bures-les-Monts", @@ -3586,63 +2168,21 @@ FR_BASE_EXCEPTIONS = [ "Burey-en-Vaux", "Burey-la-Côte", "Burg-Reuland", -"burg-reulandais", "Burg-Reulandais", "Burg-Reulandaise", +"Burkina-Faso", "Burkina-be", "Burkina-bes", -"Burkina-Faso", "Burkina-fassien", "Burnhaupt-le-Bas", "Burnhaupt-le-Haut", -"burn-out", -"burn-outa", -"burn-outai", -"burn-outaient", -"burn-outais", -"burn-outait", -"burn-outâmes", -"burn-outant", -"burn-outas", -"burn-outasse", -"burn-outassent", -"burn-outasses", -"burn-outassiez", -"burn-outassions", -"burn-outât", -"burn-outâtes", -"burn-oute", -"burn-outé", -"burn-outée", -"burn-outées", -"burn-outent", -"burn-outer", -"burn-outera", -"burn-outerai", -"burn-outeraient", -"burn-outerais", -"burn-outerait", -"burn-outeras", -"burn-outèrent", -"burn-outerez", -"burn-outeriez", -"burn-outerions", -"burn-outerons", -"burn-outeront", -"burn-outes", -"burn-outés", -"burn-outez", -"burn-outiez", -"burn-outions", -"burn-outons", -"burn-outs", "Burosse-Mendousse", "Burthecourt-aux-Chênes", +"Bus-Saint-Rémy", "Bus-la-Mésière", "Bus-lès-Artois", "Bussac-Forêt", "Bussac-sur-Charente", -"Bus-Saint-Rémy", "Busserotte-et-Montenaille", "Bussière-Badil", "Bussière-Boffy", @@ -3655,8 +2195,11 @@ FR_BASE_EXCEPTIONS = [ "Bussunarits-Sarrasquette", "Bussus-Bussuel", "Bussy-Albieux", -"Bussy-aux-Bois", "Bussy-Chardonney", +"Bussy-Lettrée", +"Bussy-Saint-Georges", +"Bussy-Saint-Martin", +"Bussy-aux-Bois", "Bussy-en-Othe", "Bussy-la-Côte", "Bussy-la-Pesle", @@ -3665,171 +2208,108 @@ FR_BASE_EXCEPTIONS = [ "Bussy-le-Repos", "Bussy-lès-Daours", "Bussy-lès-Poix", -"Bussy-Lettrée", -"Bussy-Saint-Georges", -"Bussy-Saint-Martin", "Bussy-sur-Moudon", -"buste-reliquaire", -"bustes-reliquaires", "Bustince-Iriberry", -"Butot-en-Caux", "Butot-Vénesville", +"Butot-en-Caux", "Butry-sur-Oise", -"but-sur-balles", "Butte-Montmartre", -"butter-oil", "Buttes-Chaumont", "Buxières-d'Aillac", +"Buxières-les-Mines", "Buxières-lès-Clefmont", "Buxières-lès-Froncles", -"Buxières-les-Mines", "Buxières-lès-Villiers", -"Buxières-sous-les-Côtes", "Buxières-sous-Montaigut", +"Buxières-sous-les-Côtes", "Buxières-sur-Arce", "Buzet-sur-Baïse", "Buzet-sur-Tarn", "Buzy-Darmont", -"BVD-MD", "Byans-sur-Doubs", -"bye-bye", "Byhleguhre-Byhlen", -"by-passa", -"by-passai", -"by-passaient", -"by-passais", -"by-passait", -"by-passâmes", -"by-passant", -"by-passas", -"by-passasse", -"by-passassent", -"by-passasses", -"by-passassiez", -"by-passassions", -"by-passât", -"by-passâtes", -"by-passe", -"by-passé", -"by-passée", -"by-passées", -"by-passent", -"by-passer", -"by-passera", -"by-passerai", -"by-passeraient", -"by-passerais", -"by-passerait", -"by-passeras", -"by-passèrent", -"by-passerez", -"by-passeriez", -"by-passerions", -"by-passerons", -"by-passeront", -"by-passes", -"by-passés", -"by-passez", -"by-passiez", -"by-passions", -"by-passons", +"Bœurs-en-Othe", +"Bâgé-la-Ville", +"Bâgé-le-Châtel", +"Bâle-Campagne", +"Bâle-Ville", +"Béard-Géovreissiat", +"Bécon-les-Granits", +"Bécordel-Bécourt", +"Bédeilhac-et-Aynat", +"Bédouès-Cocurès", +"Bégrolles-en-Mauges", +"Béhasque-Lapiste", +"Bélesta-en-Lauragais", +"Bénesse-Maremne", +"Bénesse-lès-Dax", +"Béning-lès-Saint-Avold", +"Bénivay-Ollon", +"Bény-Bocain", +"Bény-Bocaine", +"Bény-Bocaines", +"Bény-Bocains", +"Bény-sur-Mer", +"Bénévent-et-Charbillac", +"Bénévent-l'Abbaye", +"Bérengeville-la-Campagne", +"Bérig-Vintrange", +"Bérou-la-Mulotière", +"Béthancourt-en-Valois", +"Béthancourt-en-Vaux", +"Béthemont-la-Forêt", +"Béthencourt-sur-Mer", +"Béthencourt-sur-Somme", +"Béthisy-Saint-Martin", +"Béthisy-Saint-Pierre", +"Béville-le-Comte", +"Bézaudun-les-Alpes", +"Bézaudun-sur-Bîne", +"Bézu-Saint-Eloi", +"Bézu-Saint-Germain", +"Bézu-Saint-Éloi", +"Bézu-la-Forêt", +"Bézu-le-Guéry", +"Bézues-Bajon", +"Böhl-Iggelheim", +"Börde-Hakel", +"Börgerende-Rethwisch", +"Bösleben-Wüllersleben", +"Bœurs-en-Othe", "C-4", +"C-blanc", +"C-blancs", +"C.-Antip.", +"CD-R", +"CD-ROM", +"CD-RW", +"CD-WORM", "Cabanac-Cazaux", -"Cabanac-et-Villagrains", "Cabanac-Séguenville", -"cabane-roulotte", -"cabanes-roulottes", +"Cabanac-et-Villagrains", "Cabas-Loumassès", -"câblo-opérateur", -"câblo-opérateurs", "Cabrières-d'Aigues", "Cabrières-d'Avignon", -"cacasse-à-cul-nu", -"cacasses-à-cul-nu", -"c-à-d", -"c.-à-d.", "Cadegliano-Viconago", "Cadeilhan-Trachère", "Cadillac-en-Fronsadais", -"cadrage-débordement", "Cadzand-Bad", -"caf'conc", -"café-au-lait", -"café-bar", -"café-bistro", -"café-calva", -"café-comptoir", -"café-concert", -"café-crème", -"café-filtre", -"cafés-bars", -"cafés-concerts", -"cafés-crèmes", -"cafés-filtre", -"cafés-théâtres", -"café-théâtre", -"cages-théâtres", -"cage-théâtre", "Cagnac-les-Mines", "Cagnes-sur-Mer", -"cague-braille", -"cague-brailles", -"cahin-caha", "Cahuzac-sur-Adour", "Cahuzac-sur-Vère", -"cail-cédra", -"cail-cédras", -"cail-cédrin", -"cail-cédrins", -"caillé-blanc", -"caille-lait", -"caille-laits", -"caillés-blancs", -"cailleu-tassart", -"caillot-rosat", -"caillots-rosats", -"Caillouël-Crépigny", "Caillouet-Orgeville", "Cailloux-sur-Fontaines", +"Caillouël-Crépigny", "Cailly-sur-Eure", -"caïque-bazar", -"caïques-bazars", -"caisse-outre", -"caisse-palette", -"caisses-outres", -"caisses-palettes", -"cake-walk", -"cake-walks", "Calasca-Castiglione", "Calatafimi-Segesta", -"calcite-rhodochrosite", -"calcites-rhodochrosites", -"calcium-autunite", -"calcium-autunites", -"calcium-pyromorphite", -"calcium-pyromorphites", -"calcium-rhodochrosite", -"calcium-rhodochrosites", -"cale-bas", -"caleçon-combinaison", -"caleçons-combinaisons", -"cale-dos", -"cale-hauban", -"cale-haubans", -"cale-pied", -"cale-pieds", "Calleville-les-Deux-Eglises", "Calleville-les-Deux-Églises", -"call-girl", -"call-girls", "Calmels-et-le-Viala", -"calo-moulinotin", "Calo-Moulinotin", -"calo-moulinotine", "Calo-Moulinotine", -"calo-moulinotines", "Calo-Moulinotines", -"calo-moulinotins", "Calo-Moulinotins", "Calonne-Ricouart", "Calonne-sur-la-Lys", @@ -3849,27 +2329,20 @@ FR_BASE_EXCEPTIONS = [ "Cambon-lès-Lavaur", "Cambounet-sur-le-Sor", "Cambron-Casteau", +"Cambron-Saint-Vincent", "Cambronne-lès-Clermont", "Cambronne-lès-Ribécourt", -"Cambron-Saint-Vincent", -"came-cruse", -"caméra-lucida", -"caméra-piéton", -"caméra-piétons", "Camiac-et-Saint-Denis", -"camion-bélier", -"camion-citerne", -"camion-cuisine", -"camion-cuisines", -"camion-poubelle", -"camions-béliers", -"camions-bennes", -"camions-citernes", -"camions-poubelles", "Camou-Cihigue", "Camou-Mixe-Suhast", -"Campagnac-lès-Quercy", +"Camp-Auriol", +"Camp-Dumy", +"Camp-Mégier", +"Camp-Méjan", +"Camp-Public", +"Camp-Réal", "Campagna-de-Sault", +"Campagnac-lès-Quercy", "Campagne-d'Armagnac", "Campagne-lès-Boulonnais", "Campagne-lès-Guines", @@ -3878,10 +2351,6 @@ FR_BASE_EXCEPTIONS = [ "Campagne-sur-Arize", "Campagne-sur-Aude", "Campandré-Valcongrain", -"campanulo-infundibiliforme", -"campanulo-infundibiliformes", -"Camp-Auriol", -"Camp-Dumy", "Campestre-et-Luc", "Campet-et-Lamolère", "Campezo-Kanpezu", @@ -3892,46 +2361,23 @@ FR_BASE_EXCEPTIONS = [ "Campigneulles-les-Petites", "Campillos-Paravientos", "Campillos-Sierra", -"camping-car", -"camping-cars", -"camping-gaz", "Camping-Gaz", "Camplong-d'Aude", -"Camp-Mégier", -"Camp-Méjan", -"campo-haltien", "Campo-Haltien", -"campo-haltienne", -"campo-haltiennes", -"campo-haltiens", -"campo-laïcien", "Campo-Laïcien", -"campo-laïcienne", "Campo-Laïcienne", -"campo-laïciennes", "Campo-Laïciennes", -"campo-laïciens", "Campo-Laïciens", -"Camp-Public", -"Camp-Réal", +"Camps-Saint-Mathurin-Léobazel", "Camps-en-Amiénois", "Camps-la-Source", -"Camps-Saint-Mathurin-Léobazel", "Camps-sur-l'Agly", "Camps-sur-l'Isle", -"camps-volants", -"camp-volant", "Canada-Uni", -"canadien-français", "Canale-di-Verde", -"canapé-lit", -"canapés-lits", "Canaules-et-Argentières", -"candau-casteidois", "Candau-Casteidois", -"candau-casteidoise", "Candau-Casteidoise", -"candau-casteidoises", "Candau-Casteidoises", "Candes-Saint-Martin", "Candé-sur-Beuvron", @@ -3939,28 +2385,18 @@ FR_BASE_EXCEPTIONS = [ "Canet-de-Salars", "Canet-en-Roussillon", "Caniac-du-Causse", -"cani-joering", -"cani-rando", -"canne-épée", "Cannes-Ecluse", -"Cannes-Écluse", -"cannes-épées", "Cannes-et-Clairan", -"cannib's", +"Cannes-Écluse", "Canny-sur-Matz", "Canny-sur-Thérain", -"canoë-kayak", -"canoë-kayaks", -"canon-revolver", -"canons-revolvers", "Cantaing-sur-Escaut", "Cante-Greil", "Cante-Grel", "Cante-Grillet", +"Cante-Perdris", "Cantenay-Epinard", "Cantenay-Épinard", -"Cante-Perdris", -"C.-Antip.", "Cantonnier-de-l'Est", "Canville-la-Rocque", "Canville-les-Deux-Eglises", @@ -3968,83 +2404,34 @@ FR_BASE_EXCEPTIONS = [ "Cany-Barville", "Caorches-Saint-Nicolas", "Caouënnec-Lanvézéac", +"Cap-d'Ail", "Capaccio-Paestum", "Capdenac-Gare", "Capelle-Fermont", -"capelle-filismontin", "Capelle-Filismontin", -"capelle-filismontine", "Capelle-Filismontine", -"capelle-filismontines", "Capelle-Filismontines", -"capelle-filismontins", "Capelle-Filismontins", "Capelle-les-Grands", "Capelle-lès-Hesdin", -"capélo-hugonais", -"Capélo-Hugonais", -"capélo-hugonaise", -"Capélo-Hugonaise", -"capélo-hugonaises", -"Capélo-Hugonaises", "Capesterre-Belle-Eau", "Capesterre-de-Marie-Galante", -"capi-aga", -"capi-agas", -"capigi-bassi", -"capigi-bassis", "Capitale-Nationale", -"capital-risque", -"capital-risques", -"capital-risqueur", -"capital-risqueurs", -"capitan-pacha", -"capitan-pachas", -"capitaux-risqueurs", -"caporal-chef", -"caporaux-chefs", "Capoulet-et-Junac", "Cappelle-Brouck", "Cappelle-en-Pévèle", "Cappelle-la-Grande", -"capsule-congé", -"capsules-congés", -"capuchon-de-moine", -"caput-mortuum", -"caque-denier", -"carbo-azotine", -"carbonate-apatite", -"carbonate-apatites", +"Capélo-Hugonais", +"Capélo-Hugonaise", +"Capélo-Hugonaises", "Carbon-Blanc", -"carbone-14", -"carbones-14", "Carbonia-Iglesias", "Carcarès-Sainte-Croix", "Carcen-Ponson", -"carcere-duro", "Carcheto-Brustico", -"cardio-chirurgien", -"cardio-chirurgienne", -"cardio-chirurgiennes", -"cardio-chirurgiens", -"cardio-kickboxing", -"cardio-kickboxings", -"cardio-thoracique", -"cardio-thoraciques", -"cardio-training", -"cardio-vasculaire", -"cardio-vasculaires", "Cardo-Torgia", -"carême-prenant", -"carfentrazone-éthyle", -"car-ferries", -"car-ferry", -"car-ferrys", -"cargo-dortoir", -"cargos-dortoirs", "Carhaix-Plouguer", "Carignan-de-Bordeaux", -"car-jacking", "Carla-Bayle", "Carla-de-Roquefort", "Carla-le-Comte", @@ -4052,16 +2439,9 @@ FR_BASE_EXCEPTIONS = [ "Carmzow-Wallmow", "Carnac-Rouffiac", "Carnoux-en-Provence", -"caro-percyais", "Caro-Percyais", -"caro-percyaise", "Caro-Percyaise", -"caro-percyaises", "Caro-Percyaises", -"carré-bossu", -"carrée-bossue", -"carrées-bossues", -"carrés-bossus", "Carresse-Cassaber", "Carrières-sous-Poissy", "Carrières-sur-Seine", @@ -4069,210 +2449,98 @@ FR_BASE_EXCEPTIONS = [ "Carsac-Aillac", "Carsac-de-Gurson", "Carsac-de-Villefranche", -"carte-cadeau", -"carte-fille", -"carte-index", -"carte-lettre", -"carte-maximum", -"carte-mère", -"cartes-cadeaux", -"cartes-filles", -"cartes-lettres", -"cartes-maximum", -"cartes-mères", -"carte-soleil", -"cartes-vues", -"carte-vue", "Cartigny-l'Epinay", "Cartigny-l'Épinay", -"carton-index", -"carton-pâte", -"carton-pierre", -"cartons-pâte", -"Carville-la-Folletière", "Carville-Pot-de-Fer", +"Carville-la-Folletière", "Cascastel-des-Corbières", "Case-Pilote", "Cases-de-Pène", -"cash-back", -"cash-flow", -"cash-flows", -"cas-limite", -"cas-limites", -"casque-de-Jupiter", "Cassagnabère-Tournas", "Cassagnes-Bégonhès", -"casse-aiguille", -"casse-bélier", -"casse-béliers", -"casse-bonbon", -"casse-bonbons", -"casse-bouteille", -"casse-bras", -"casse-burnes", -"casse-claouis", -"casse-coeur", -"casse-cœur", -"casse-coeurs", -"casse-cœurs", -"casse-cou", -"casse-couille", -"casse-couilles", -"casse-cous", -"casse-croute", -"casse-croûte", -"casse-croutes", -"casse-croûtes", -"casse-cul", -"casse-culs", -"casse-dalle", -"casse-dalles", -"casse-fer", -"casse-fil", -"casse-fils", -"casse-graine", -"casse-graines", -"casse-gueule", -"casse-gueules", -"casse-langue", -"casse-langues", -"casse-lunette", -"casse-lunettes", -"casse-mariages", -"casse-motte", -"casse-museau", -"casse-museaux", -"casse-noisette", -"casse-noisettes", -"casse-noix", -"casse-nole", -"casse-noyaux", -"casse-olives", -"casse-patte", -"casse-pattes", -"casse-péter", -"casse-pied", -"casse-pieds", -"casse-pierre", -"casse-pierres", -"casse-pipe", -"casse-pipes", -"casse-poitrine", -"casse-pot", -"casse-tête", -"casse-têtes", -"casse-vessie", -"cassi-ascher", -"cassi-aschers", "Castaignos-Souslens", -"Castanet-le-Haut", "Castanet-Tolosan", +"Castanet-le-Haut", "Casteide-Cami", "Casteide-Candau", "Casteide-Doat", -"castel-ambillouçois", "Castel-Ambillouçois", -"castel-ambillouçoise", "Castel-Ambillouçoise", -"castel-ambillouçoises", "Castel-Ambillouçoises", -"Castelbello-Ciardes", -"castel-chalonnais", "Castel-Chalonnais", -"castel-chalonnaise", "Castel-Chalonnaise", -"castel-chalonnaises", "Castel-Chalonnaises", +"Castel-Lévézien", +"Castel-Lévézienne", +"Castel-Lévéziennes", +"Castel-Lévéziens", +"Castel-Pontin", +"Castel-Pontine", +"Castel-Pontines", +"Castel-Pontins", +"Castel-Sarrazin", +"Castel-Symphorinois", +"Castel-Symphorinoise", +"Castel-Symphorinoises", +"Castelbello-Ciardes", "Castell'Alfero", -"Castellare-di-Casinca", -"Castellare-di-Mercurio", "Castell'Arquato", "Castell'Azzara", -"Castellet-lès-Sausses", -"castel-lévézien", -"Castel-Lévézien", -"castel-lévézienne", -"Castel-Lévézienne", -"castel-lévéziennes", -"Castel-Lévéziennes", -"castel-lévéziens", -"Castel-Lévéziens", -"Castello-di-Rostino", "Castell'Umberto", +"Castellare-di-Casinca", +"Castellare-di-Mercurio", +"Castellet-lès-Sausses", +"Castello-di-Rostino", "Castelmoron-d'Albret", "Castelmoron-sur-Lot", +"Castelnau d'Auzan Labarrère", "Castelnau-Barbarens", "Castelnau-Chalosse", +"Castelnau-Durban", +"Castelnau-Durbannais", +"Castelnau-Durbannaise", +"Castelnau-Durbannaises", +"Castelnau-Magnoac", +"Castelnau-Montratier", +"Castelnau-Montratier-Sainte-Alauzie", +"Castelnau-Picampeau", +"Castelnau-Pégayrols", +"Castelnau-Rivière-Basse", +"Castelnau-Tursan", +"Castelnau-Valence", "Castelnau-d'Anglès", "Castelnau-d'Arbieu", "Castelnau-d'Aude", "Castelnau-d'Auzan", -"Castelnaud-de-Gratecambe", +"Castelnau-d'Estrétefonds", "Castelnau-de-Brassac", "Castelnau-de-Guers", "Castelnau-de-Lévis", "Castelnau-de-Mandailles", -"Castelnau-de-Médoc", "Castelnau-de-Montmiral", -"Castelnau-d'Estrétefonds", -"Castelnaud-la-Chapelle", -"Castelnau-Durban", -"castelnau-durbannais", -"Castelnau-Durbannais", -"castelnau-durbannaise", -"Castelnau-Durbannaise", -"castelnau-durbannaises", -"Castelnau-Durbannaises", +"Castelnau-de-Médoc", "Castelnau-le-Lez", -"Castelnau-Magnoac", -"Castelnau-Montratier", -"Castelnau-Montratier-Sainte-Alauzie", -"Castelnau-Pégayrols", -"Castelnau-Picampeau", -"Castelnau-Rivière-Basse", "Castelnau-sur-Gupie", "Castelnau-sur-l'Auvignon", -"Castelnau-Tursan", -"Castelnau-Valence", -"castel-pontin", -"Castel-Pontin", -"castel-pontine", -"Castel-Pontine", -"castel-pontines", -"Castel-Pontines", -"castel-pontins", -"Castel-Pontins", -"Castel-Sarrazin", +"Castelnaud-de-Gratecambe", +"Castelnaud-la-Chapelle", "Castels-et-Bézenac", -"castel-symphorinois", -"Castel-Symphorinois", -"castel-symphorinoise", -"Castel-Symphorinoise", -"castel-symphorinoises", -"Castel-Symphorinoises", -"Castéra-Bouzet", -"Castéra-Lanusse", -"Castéra-Lectourois", -"Castéra-Lou", -"Castéra-Loubix", -"Castéra-Verduzan", -"Castéra-Vignoles", "Castet-Arrouy", -"castet-arrouyais", "Castet-Arrouyais", -"castet-arrouyaise", "Castet-Arrouyaise", -"castet-arrouyaises", "Castet-Arrouyaises", "Castetnau-Camblong", "Castets-en-Dorthe", "Castex-d'Armagnac", +"Casti-Wergenstein", "Casties-Labrande", -"castillano-aragonais", "Castille-et-León", "Castillejo-Sierra", "Castillo-Albaráñez", +"Castillon (Canton d'Arthez-de-Béarn)", "Castillon-Debats", +"Castillon-Massas", +"Castillon-Savès", "Castillon-de-Castets", "Castillon-de-Larboust", "Castillon-de-Saint-Martory", @@ -4281,20 +2549,19 @@ FR_BASE_EXCEPTIONS = [ "Castillon-en-Couserans", "Castillon-et-Capitourlan", "Castillon-la-Bataille", -"Castillon-Massas", -"Castillon-Savès", -"Casti-Wergenstein", "Castres-Gironde", "Castrillo-Tejeriego", -"Castrop-Rauxel", "Castro-Urdiales", -"catalan-valencien-baléare", -"catalase-positive", -"cat-boat", +"Castrop-Rauxel", +"Castéra-Bouzet", +"Castéra-Lanusse", +"Castéra-Lectourois", +"Castéra-Lou", +"Castéra-Loubix", +"Castéra-Verduzan", +"Castéra-Vignoles", "Catillon-Fumechon", "Catillon-sur-Sambre", -"cato-cathartique", -"cato-cathartiques", "Caubios-Loos", "Caubon-Saint-Sauveur", "Cauchy-à-la-Tour", @@ -4309,8 +2576,8 @@ FR_BASE_EXCEPTIONS = [ "Caumont-sur-Garonne", "Caumont-sur-Orne", "Caunes-Minervois", -"Caunettes-en-Val", "Caunette-sur-Lauquet", +"Caunettes-en-Val", "Caupenne-d'Armagnac", "Cauroy-lès-Hermonville", "Cause-de-Clérans", @@ -4322,96 +2589,35 @@ FR_BASE_EXCEPTIONS = [ "Cauverville-en-Roumois", "Cauville-sur-Mer", "Caux-et-Sauzens", -"ça-va-ça-vient", "Cavaglio-Spoccia", "Cavalaire-sur-Mer", "Cavallino-Treporti", -"ça-voir", -"ça-voirs", "Cavron-Saint-Martin", "Cayeux-en-Santerre", "Cayeux-sur-Mer", "Cayre-four", "Cazals-des-Baylès", -"Cazarilh-Laspènes", "Cazaril-Laspènes", "Cazaril-Tambourès", -"Cazaux-d'Anglès", +"Cazarilh-Laspènes", "Cazaux-Debat", "Cazaux-Fréchet-Anéran-Camors", "Cazaux-Layrisse", "Cazaux-Savès", "Cazaux-Villecomtal", +"Cazaux-d'Anglès", "Cazeaux-de-Larboust", "Cazenave-Serres-et-Allens", "Cazeneuve-Montaut", -"Cazères-sur-l'Adour", "Cazes-Mondenard", "Cazouls-d'Hérault", "Cazouls-lès-Béziers", -"C-blanc", -"C-blancs", -"c-commanda", -"c-commandai", -"c-commandaient", -"c-commandais", -"c-commandait", -"c-commandâmes", -"c-commandant", -"c-commandas", -"c-commandasse", -"c-commandassent", -"c-commandasses", -"c-commandassiez", -"c-commandassions", -"c-commandât", -"c-commandâtes", -"c-commande", -"c-commandé", -"c-commandée", -"c-commandées", -"c-commandent", -"c-commander", -"c-commandera", -"c-commanderai", -"c-commanderaient", -"c-commanderais", -"c-commanderait", -"c-commanderas", -"c-commandèrent", -"c-commanderez", -"c-commanderiez", -"c-commanderions", -"c-commanderons", -"c-commanderont", -"c-commandes", -"c-commandés", -"c-commandez", -"c-commandiez", -"c-commandions", -"c-commandons", -"CD-R", -"CD-ROM", -"CD-RW", -"CD-WORM", -"Céaux-d'Allègre", +"Cazères-sur-l'Adour", "Ceaux-en-Couhé", "Ceaux-en-Loudun", -"cédez-le-passage", "Ceilhes-et-Rocozels", -"cejourd'hui", -"céleri-rave", -"cèleri-rave", -"céléri-rave", -"cèleri-raves", -"céleris-raves", -"Céleste-Empire", -"celle-ci", -"celle-là", "Celle-Lévescault", -"celles-ci", "Celles-en-Bassigny", -"celles-là", "Celles-lès-Condé", "Celles-sur-Aisne", "Celles-sur-Belle", @@ -4419,63 +2625,19 @@ FR_BASE_EXCEPTIONS = [ "Celles-sur-Ource", "Celles-sur-Plaine", "Cellier-du-Luc", -"celto-nordique", -"celto-nordiques", -"celui-ci", -"celui-là", -"Cély-en-Bière", -"Cénac-et-Saint-Julien", "Cenne-Monestiés", "Cenon-sur-Vienne", -"cent-cinquante-cinq", -"cent-cinquante-cinquièmes", -"cent-garde", -"cent-gardes", -"cent-lances", -"cent-mille", -"centre-bourg", -"centre-droit", -"Centre-du-Québec", "Centre-Est", -"centre-gauche", "Centre-Mauricien", "Centre-Nord", "Centre-Ouest", -"centres-bourgs", "Centre-Sud", -"centres-villes", -"centre-tir", -"centre-ville", +"Centre-du-Québec", "Centro-Américain", "Centro-Américaine", "Centro-Américains", -"cent-suisse", -"cent-suisses", -"céphalo-pharyngien", -"céphalo-pharyngienne", -"céphalo-pharyngiennes", -"céphalo-pharyngiens", -"céphalo-rachidien", -"Cérans-Foulletourte", "Cercy-la-Tour", -"cérébro-lésion", -"cérébro-lésions", -"cérébro-rachidien", -"cérébro-rachidienne", -"cérébro-rachidiennes", -"cérébro-rachidiens", -"cérébro-spinal", -"cérébro-spinale", -"cérébro-spinales", -"cérébro-spinaux", -"Céré-la-Ronde", "Cerexhe-Heuseux", -"cerfs-veaux", -"cerfs-volants", -"cerfs-volistes", -"cerf-veau", -"cerf-volant", -"cerf-voliste", "Cerisy-Belle-Etoile", "Cerisy-Belle-Étoile", "Cerisy-Buleux", @@ -4483,60 +2645,34 @@ FR_BASE_EXCEPTIONS = [ "Cerisy-la-Forêt", "Cerisy-la-Salle", "Cernay-en-Dormois", -"Cernay-la-Ville", "Cernay-l'Eglise", "Cernay-l'Église", +"Cernay-la-Ville", "Cernay-lès-Reims", "Cernoy-en-Berry", "Cerny-en-Laonnois", "Cerny-lès-Bucy", -"Céroux-Mousty", "Cerre-lès-Noroy", -"certificat-cadeau", -"césaro-papisme", -"césaro-papismes", -"césaro-papiste", -"césaro-papistes", -"Césarville-Dossainville", -"césium-analcime", -"césium-analcimes", -"Cesny-aux-Vignes", "Cesny-Bois-Halbout", -"cesoird'hui", +"Cesny-aux-Vignes", "Cessenon-sur-Orb", "Cessey-sur-Tille", -"cessez-le-feu", -"cession-bail", "Cesson-Sévigné", "Cessoy-en-Montois", "Cessy-les-Bois", -"c'est-à-dire", -"cesta-punta", "Cette-Eygun", -"ceux-ci", -"ceux-là", -"chabada-bada", -"cha'ban", -"chabazite-Ca", -"chabazite-Cas", -"chabazite-Na", -"chabazite-Nas", -"cha-cha", -"cha-cha-cha", -"cha-chas", "Chagny-lès-Omont", -"Chaillac-sur-Vienne", -"Chaillé-les-Marais", "Chail-les-Bains", -"Chaillé-sous-les-Ormeaux", +"Chaillac-sur-Vienne", "Chailly-en-Bière", "Chailly-en-Brie", "Chailly-en-Gâtinais", "Chailly-lès-Ennery", "Chailly-sur-Armançon", "Chailly-sur-Montreux", +"Chaillé-les-Marais", +"Chaillé-sous-les-Ormeaux", "Chainaz-les-Frasses", -"Chaînée-des-Coupis", "Chaintrix-Bierges", "Chaise-Dieu-du-Theil", "Chalain-d'Uzore", @@ -4545,7 +2681,6 @@ FR_BASE_EXCEPTIONS = [ "Chalautre-la-Grande", "Chalautre-la-Petite", "Chalautre-la-Reposte", -"Châlette-sur-Loing", "Chalette-sur-Voire", "Chalivoy-Milon", "Challain-la-Potherie", @@ -4554,27 +2689,20 @@ FR_BASE_EXCEPTIONS = [ "Challes-la-Montagne", "Challes-les-Eaux", "Chalmazel-Jeansagnière", +"Chalo-Saint-Mars", +"Chalon-sur-Saône", "Chalonnes-sous-le-Lude", "Chalonnes-sur-Loire", -"Châlon's", -"Châlons-du-Maine", "Chalons-en-Champagne", -"Châlons-en-Champagne", -"Châlons-sur-Marne", -"Châlons-sur-Vesle", -"Chalon-sur-Saône", -"Chalo-Saint-Mars", "Chalou-Moulineux", "Chamalières-sur-Loire", "Chamarandes-Choignes", "Chambaron-sur-Morge", -"Chambéry-le-Vieux", "Chambley-Bussières", -"chambolle-musigny", "Chambolle-Musigny", +"Chambon-Sainte-Croix", "Chambon-la-Forêt", "Chambon-le-Château", -"Chambon-Sainte-Croix", "Chambon-sur-Cisse", "Chambon-sur-Dolore", "Chambon-sur-Lac", @@ -4583,13 +2711,19 @@ FR_BASE_EXCEPTIONS = [ "Chambornay-lès-Pin", "Chambost-Allières", "Chambost-Longessaigne", -"chamboule-tout", "Chambourg-sur-Indre", "Chambray-lès-Tours", -"chamito-sémitique", -"chamito-sémitiques", +"Chambéry-le-Vieux", "Chamonix-Mont-Blanc", "Chamoux-sur-Gelon", +"Champ-Dolent", +"Champ-Haut", +"Champ-Laurent", +"Champ-d'Oiseau", +"Champ-du-Boult", +"Champ-le-Duc", +"Champ-sur-Barse", +"Champ-sur-Drac", "Champagnac-de-Belair", "Champagnac-la-Noaille", "Champagnac-la-Prune", @@ -4597,21 +2731,21 @@ FR_BASE_EXCEPTIONS = [ "Champagnac-le-Vieux", "Champagnat-le-Jeune", "Champagne-Ardenne", +"Champagne-Mouton", +"Champagne-Vigny", "Champagne-au-Mont-d'Or", "Champagne-de-Blanzac", "Champagne-en-Valromey", "Champagne-et-Fontaine", -"Champagné-le-Sec", -"Champagné-les-Marais", -"Champagne-Mouton", -"Champagné-Saint-Hilaire", "Champagne-sur-Loue", "Champagne-sur-Oise", "Champagne-sur-Seine", "Champagne-sur-Vingeanne", -"Champagne-Vigny", "Champagny-en-Vanoise", "Champagny-sous-Uxelles", +"Champagné-Saint-Hilaire", +"Champagné-le-Sec", +"Champagné-les-Marais", "Champaubert-aux-Bois", "Champdeniers-Saint-Denis", "Champdor-Corcelles", @@ -4620,8 +2754,8 @@ FR_BASE_EXCEPTIONS = [ "Champeaux-sur-Sarthe", "Champey-sur-Moselle", "Champigneul-Champagne", -"Champigneulles-en-Bassigny", "Champigneul-sur-Vence", +"Champigneulles-en-Bassigny", "Champignol-lez-Mondeville", "Champigny-en-Beauce", "Champigny-en-Rochereau", @@ -4638,13 +2772,12 @@ FR_BASE_EXCEPTIONS = [ "Champniers-et-Reilhac", "Champrond-en-Gâtine", "Champrond-en-Perchet", -"champs-clos", -"Champs-Élysées", "Champs-Romain", "Champs-sur-Marne", "Champs-sur-Tarentaine-Marchal", "Champs-sur-Yonne", "Champs-zé", +"Champs-Élysées", "Champteussé-sur-Baconne", "Champtocé-sur-Loire", "Champvans-les-Baume", @@ -4655,15 +2788,11 @@ FR_BASE_EXCEPTIONS = [ "Chanceaux-sur-Choisille", "Chang-Haï", "Changis-sur-Marne", -"changxing'ien", "Changxing'ien", "Channay-sur-Lathan", "Chanos-Curson", -"chanos-cursonnais", "Chanos-Cursonnais", -"chanos-cursonnaise", "Chanos-Cursonnaise", -"chanos-cursonnaises", "Chanos-Cursonnaises", "Chanoz-Châtenay", "Chante-Clair", @@ -4675,70 +2804,28 @@ FR_BASE_EXCEPTIONS = [ "Chantemerle-sur-la-Soie", "Chantenay-Saint-Imbert", "Chantenay-Villedieu", -"chantilly-tiffany", "Chapdes-Beaufort", -"chape-chuta", -"chape-chutai", -"chape-chutaient", -"chape-chutais", -"chape-chutait", -"chape-chutâmes", -"chape-chutant", -"chape-chutas", -"chape-chutasse", -"chape-chutassent", -"chape-chutasses", -"chape-chutassiez", -"chape-chutassions", -"chape-chutât", -"chape-chutâtes", -"chape-chute", -"chape-chuté", -"chape-chutent", -"chape-chuter", -"chape-chutera", -"chape-chuterai", -"chape-chuteraient", -"chape-chuterais", -"chape-chuterait", -"chape-chuteras", -"chape-chutèrent", -"chape-chuterez", -"chape-chuteriez", -"chape-chuterions", -"chape-chuterons", -"chape-chuteront", -"chape-chutes", -"chape-chutez", -"chape-chutiez", -"chape-chutions", -"chape-chutons", -"chapelloise-fortinienne", -"Chapelloise-Fortinienne", -"chapelloises-fortiniennes", -"Chapelloises-Fortiniennes", -"chapellois-fortinien", +"Chapelle-Guillaume", +"Chapelle-Royale", +"Chapelle-Spinasse", +"Chapelle-Vallon", +"Chapelle-Viviers", +"Chapelle-Voland", +"Chapelle-d'Huin", +"Chapelle-des-Bois", "Chapellois-Fortinien", -"chapellois-fortiniens", "Chapellois-Fortiniens", +"Chapelloise-Fortinienne", +"Chapelloises-Fortiniennes", "Chapon-Seraing", -"chapon-sérésien", "Chapon-Sérésien", "Chapon-Sérésienne", -"char-à-bancs", -"charbon-de-pierre", -"charbon-de-terre", +"Charbonnier-les-Mines", "Charbonnières-les-Bains", "Charbonnières-les-Sapins", "Charbonnières-les-Varennes", "Charbonnières-les-Vieilles", -"Charbonnier-les-Mines", -"charbons-de-pierre", -"charbons-de-terre", "Charcé-Saint-Ellier-sur-Aubance", -"chardon-Marie", -"chardon-Roland", -"chardons-Marie", "Chareil-Cintrat", "Charency-Vezin", "Charente-Inférieure", @@ -4746,7 +2833,6 @@ FR_BASE_EXCEPTIONS = [ "Charenton-du-Cher", "Charenton-le-Pont", "Charette-Varennes", -"chargeuse-pelleteuse", "Chargey-lès-Gray", "Chargey-lès-Port", "Charles-Quint", @@ -4755,20 +2841,18 @@ FR_BASE_EXCEPTIONS = [ "Charlevoisien-de-l'Est", "Charly-Oradour", "Charly-sur-Marne", -"charme-houblon", +"Charmes-Saint-Valbert", "Charmes-en-l'Angle", -"charmes-houblons", "Charmes-la-Côte", "Charmes-la-Grande", -"Charmes-Saint-Valbert", -"Charmes-sur-l'Herbasse", "Charmes-sur-Rhône", +"Charmes-sur-l'Herbasse", "Charmois-devant-Bruyères", "Charmois-l'Orgueilleux", "Charmont-en-Beauce", -"Charmontois-l'Abbé", "Charmont-sous-Barbuise", "Charmont-sur-Marne", +"Charmontois-l'Abbé", "Charnay-lès-Chalon", "Charnay-lès-Mâcon", "Charnoz-sur-Ain", @@ -4777,169 +2861,323 @@ FR_BASE_EXCEPTIONS = [ "Charrey-sur-Saône", "Charrey-sur-Seine", "Charritte-de-Bas", -"chars-à-bancs", -"charte-partie", "Chartres-de-Bretagne", "Chartrier-Ferrière", "Charvieu-Chavagneux", "Chasné-sur-Illet", "Chassagne-Montrachet", "Chassagne-Saint-Denis", -"chasse-avant", -"chasse-bondieu", -"chasse-bondieux", -"chasse-carrée", -"chasse-carrées", -"chasse-chien", -"chasse-chiens", -"chasse-clou", -"chasse-clous", -"chasse-cœur", -"chasse-coquin", -"chasse-cousin", -"chasse-cousins", -"chasse-crapaud", -"chassé-croisé", -"chasse-derrière", -"chasse-derrières", -"chasse-diable", -"chasse-diables", -"chasse-ennui", -"chasse-fièvre", -"chasse-fleurée", -"chasse-fleurées", -"chasse-goupille", -"chasse-goupilles", -"chasse-gueux", -"chasse-marée", -"chasse-marées", -"chasse-morte", -"chasse-mouche", -"chasse-mouches", -"chasse-mulet", -"chasse-mulets", -"chasse-neige", -"chasse-neiges", +"Chasse-sur-Rhône", "Chasseneuil-du-Poitou", "Chasseneuil-sur-Bonnieure", -"chasse-noix", -"chasse-partie", -"chasse-parties", -"chasse-pierre", -"chasse-pierres", -"chasse-poignée", -"chasse-pointe", -"chasse-pointes", -"chasse-pommeau", -"chasse-punaise", -"chasse-rivet", -"chasse-rivets", -"chasse-rondelle", -"chasse-roue", -"chasse-roues", -"chassés-croisés", -"chasses-parties", -"Chasse-sur-Rhône", -"chasse-taupe", -"chasseur-bombardier", -"chasseur-cueilleur", -"chasseurs-bombardiers", -"chasseurs-cueilleurs", "Chassey-Beaupré", "Chassey-le-Camp", "Chassey-lès-Montbozon", "Chassey-lès-Scey", -"chassez-déchassez", -"chassez-huit", "Chassigny-sous-Dun", -"châssis-support", -"châssis-supports", "Chastel-Arnaud", -"Chastellux-sur-Cure", "Chastel-Nouvel", "Chastel-sur-Murat", +"Chastellux-sur-Cure", "Chastenay-le-Bas", "Chastenay-le-Haut", "Chastre-Villeroux-Blanmont", -"châtaigne-d'eau", -"châtaigne-de-mer", -"châtaignes-d'eau", -"châtaignes-de-mer", +"Chatel-Chéhéry", +"Chatenay-Mâcheron", +"Chatenay-Vaudin", +"Chatonrupt-Sommermont", +"Chatuzange-le-Goubet", +"Chauconin-Neufmontiers", +"Chaudefonds-sur-Layon", +"Chaudenay-la-Ville", +"Chaudenay-le-Château", +"Chaudeney-sur-Moselle", +"Chaudes-Aigues", +"Chaudière-Appalaches", +"Chaudon-Norante", +"Chaudron-en-Mauges", +"Chauffour-lès-Bailly", +"Chauffour-lès-Etréchy", +"Chauffour-lès-Étréchy", +"Chauffour-sur-Vell", +"Chaufour-Notre-Dame", +"Chaufour-lès-Bonnières", +"Chaume-et-Courchamp", +"Chaume-lès-Baigneux", +"Chaumes-en-Brie", +"Chaumes-en-Retz", +"Chaumont-Gistoux", +"Chaumont-Porcien", +"Chaumont-Saint-Quentin", +"Chaumont-d'Anjou", +"Chaumont-devant-Damvillers", +"Chaumont-en-Vexin", +"Chaumont-la-Ville", +"Chaumont-le-Bois", +"Chaumont-le-Bourg", +"Chaumont-sur-Aire", +"Chaumont-sur-Loire", +"Chaumont-sur-Tharonne", +"Chaumoux-Marcilly", +"Chaussoy-Epagny", +"Chaussée-Notre-Dame-Louvignies", +"Chauvac-Laux-Montaux", +"Chauvency-Saint-Hubert", +"Chauvency-le-Château", +"Chauvigny-du-Perche", +"Chauvincourt-Provemont", +"Chauvirey-le-Châtel", +"Chauvirey-le-Vieil", +"Chaux-Champagny", +"Chaux-Neuve", +"Chaux-de-Fonnier", +"Chaux-des-Crotenay", +"Chaux-des-Prés", +"Chaux-la-Lotière", +"Chaux-lès-Clerval", +"Chaux-lès-Passavant", +"Chaux-lès-Port", +"Chavagnes-en-Paillers", +"Chavagnes-les-Redoux", +"Chavagneux-Montbertand", +"Chavaniac-Lafayette", +"Chavannes-de-Bogis", +"Chavannes-des-Bois", +"Chavannes-le-Chêne", +"Chavannes-le-Veyron", +"Chavannes-les-Grands", +"Chavannes-près-Renens", +"Chavannes-sur-Moudon", +"Chavannes-sur-Reyssouze", +"Chavannes-sur-Suran", +"Chavannes-sur-l'Etang", +"Chavannes-sur-l'Étang", +"Chavigny-Bailleul", +"Chavot-Courcourt", +"Chazay-d'Azergues", +"Chazelles-sur-Albe", +"Chazelles-sur-Lavieu", +"Chazelles-sur-Lyon", +"Chazey-Bons", +"Chazey-sur-Ain", +"Chazé-Henry", +"Chazé-sur-Argos", +"Chaînée-des-Coupis", +"Chef-Boutonnais", +"Chef-Boutonnaise", +"Chef-Boutonnaises", +"Chef-Boutonne", +"Chef-Haut", +"Chef-du-Pont", +"Cheffreville-Tonnencourt", +"Cheignieu-la-Balme", +"Cheilly-lès-Maranges", +"Chein-Dessus", +"Cheix-en-Retz", +"Chelle-Debat", +"Chelle-Spou", +"Chemilly-les-Raves", +"Chemilly-près-Seignelay", +"Chemilly-sur-Serein", +"Chemilly-sur-Yonne", +"Chemillé-Melay", +"Chemillé-en-Anjou", +"Chemillé-sur-Dême", +"Chemillé-sur-Indrois", +"Chemin-d'Aisey", +"Chemiré-en-Charnie", +"Chemiré-le-Gaudin", +"Chemiré-sur-Sarthe", +"Chenac-Saint-Seurin-d'Uzet", +"Chenailler-Mascheix", +"Chenay-le-Châtel", +"Chenecey-Buillon", +"Chenevrey-et-Morogne", +"Chenillé-Champteussé", +"Chenillé-Changé", +"Chennery-et-Landreville", +"Chennevières-lès-Louvres", +"Chennevières-sur-Marne", +"Chens-sur-Léman", +"Cheppes-la-Prairie", +"Cherbourg-Octeville", +"Cherbourg-en-Cotentin", +"Chermizy-Ailles", +"Cherveix-Cubas", +"Cherves-Châtelars", +"Cherves-Richemont", +"Chesalles-sur-Moudon", +"Cheseaux-Noréaz", +"Cheseaux-sur-Lausanne", +"Chesne-Arnoul", +"Chesne-Carré", +"Chesne-Dolley", +"Chesnois-Auboncourt", +"Chessy-les-Prés", +"Chester-le-Street", +"Chevagny-les-Chevrières", +"Chevagny-sur-Guye", +"Chevaigné-du-Maine", +"Cheval-Blanc", +"Chevannes-Changy", +"Chevigney-lès-Vercel", +"Chevigney-sur-l'Ognon", +"Chevigny-Saint-Sauveur", +"Chevigny-en-Valière", +"Chevillon-sur-Huillard", +"Chevilly-Larue", +"Cheviré-le-Rouge", +"Chevresis-Monceau", +"Chevry-Cossigny", +"Chevry-en-Sereine", +"Chevry-sous-le-Bignon", +"Cheylard-l'Evêque", +"Cheylard-l'Évêque", +"Chezal-Benoît", +"Chibougamo-Chapien", +"Chigny-les-Roses", +"Chilleurs-aux-Bois", +"Chilly-Mazarin", +"Chilly-le-Vignoble", +"Chilly-sur-Salins", +"Chiopris-Viscone", +"Chirac-Bellevue", +"Chirat-l'Eglise", +"Chirat-l'Église", +"Chiry-Ourscamp", +"Chiry-Ourscamps", +"Chiré-en-Montreuil", +"Chissay-en-Touraine", +"Chissey-en-Morvan", +"Chissey-lès-Mâcon", +"Chissey-sur-Loue", +"Chitry-les-Mines", +"Chivres-Val", +"Chivres-en-Laonnois", +"Chivy-lès-Etouvelles", +"Chivy-lès-Étouvelles", +"Choilley-Dardenay", +"Choisy-au-Bac", +"Choisy-en-Brie", +"Choisy-la-Victoire", +"Choisy-le-Roi", +"Choloy-Ménillot", +"Chonas-l'Amballan", +"Chonville-Malaumont", +"Choqueuse-les-Bénards", +"Chorey-les-Beaune", +"Chouzy-sur-Cisse", +"Chouzé-sur-Loire", +"Chuffilly-Roche", +"Châlette-sur-Loing", +"Châlon's", +"Châlons-du-Maine", +"Châlons-en-Champagne", +"Châlons-sur-Marne", +"Châlons-sur-Vesle", +"Château-Arnoux-Saint-Auban", +"Château-Bernard", +"Château-Bréhain", +"Château-Chalon", +"Château-Chervix", +"Château-Chinon (Campagne)", +"Château-Chinon (Ville)", +"Château-Gaillard", +"Château-Garnier", +"Château-Gontier", +"Château-Guibert", +"Château-Landon", +"Château-Larcher", +"Château-Porcien", +"Château-Renard", +"Château-Renault", +"Château-Rouge", +"Château-Salins", +"Château-Thierry", +"Château-Thébaud", +"Château-Verdun", +"Château-Ville-Vieille", +"Château-Voué", +"Château-d'Olonne", +"Château-des-Prés", +"Château-du-Loir", +"Château-l'Abbaye", +"Château-l'Hermitage", +"Château-l'Évêque", +"Château-la-Vallière", +"Château-sur-Allier", +"Château-sur-Cher", +"Château-sur-Epte", "Châteauneuf-Calcernier", +"Châteauneuf-Grasse", +"Châteauneuf-Miravail", +"Châteauneuf-Val-Saint-Donat", +"Châteauneuf-Val-de-Bargis", +"Châteauneuf-Villevieille", +"Châteauneuf-d'Entraunes", +"Châteauneuf-d'Ille-et-Vilaine", +"Châteauneuf-d'Isère", +"Châteauneuf-d'Oze", "Châteauneuf-de-Bordette", "Châteauneuf-de-Chabre", "Châteauneuf-de-Contes", "Châteauneuf-de-Gadagne", "Châteauneuf-de-Galaure", -"Châteauneuf-d'Entraunes", "Châteauneuf-de-Randon", "Châteauneuf-de-Vernoux", -"Châteauneuf-d'Ille-et-Vilaine", -"Châteauneuf-d'Isère", -"Châteauneuf-d'Oze", "Châteauneuf-du-Faou", -"châteauneuf-du-pape", "Châteauneuf-du-Pape", "Châteauneuf-du-Rhône", "Châteauneuf-en-Thymerais", -"Châteauneuf-Grasse", "Châteauneuf-la-Forêt", "Châteauneuf-le-Rouge", "Châteauneuf-les-Bains", "Châteauneuf-les-Martigues", "Châteauneuf-lès-Moustiers", -"Châteauneuf-Miravail", "Châteauneuf-sur-Charente", "Châteauneuf-sur-Cher", "Châteauneuf-sur-Isère", "Châteauneuf-sur-Loire", "Châteauneuf-sur-Sarthe", -"Châteauneuf-Val-de-Bargis", -"Châteauneuf-Val-Saint-Donat", -"Châteauneuf-Villevieille", "Châteauroux-les-Alpes", "Châteauvieux-les-Fossés", -"châteaux-forts", -"Châtelaillon-Plage", "Châtel-Censoir", -"Chatel-Chéhéry", +"Châtel-Guyon", +"Châtel-Gérard", +"Châtel-Montagne", +"Châtel-Moron", +"Châtel-Saint-Denis", +"Châtel-Saint-Germain", "Châtel-de-Joux", "Châtel-de-Neuvre", "Châtel-en-Trièves", -"Châtel-Gérard", -"Châtel-Guyon", -"Châtel-Montagne", -"Châtel-Moron", -"Châtelraould-Saint-Louvent", -"Châtel-Saint-Denis", -"Châtel-Saint-Germain", "Châtel-sur-Montsalvens", "Châtel-sur-Moselle", -"Châtelus-le-Marcheix", +"Châtelaillon-Plage", +"Châtelraould-Saint-Louvent", "Châtelus-Malvaleix", -"Châtenay-en-France", -"Chatenay-Mâcheron", +"Châtelus-le-Marcheix", "Châtenay-Malabry", +"Châtenay-en-France", "Châtenay-sur-Seine", -"Chatenay-Vaudin", "Châtenois-les-Forges", "Châtenoy-en-Bresse", "Châtenoy-le-Royal", "Châtillon-Coligny", +"Châtillon-Guyotte", +"Châtillon-Saint-Jean", "Châtillon-en-Bazois", "Châtillon-en-Diois", "Châtillon-en-Dunois", "Châtillon-en-Michaille", "Châtillon-en-Vendelais", -"Châtillon-Guyotte", "Châtillon-la-Borde", "Châtillon-la-Palud", "Châtillon-le-Duc", "Châtillon-le-Roi", "Châtillon-lès-Sons", -"Châtillon-Saint-Jean", -"Châtillon-sous-les-Côtes", "Châtillon-sous-Maîche", +"Châtillon-sous-les-Côtes", "Châtillon-sur-Bar", "Châtillon-sur-Broué", "Châtillon-sur-Chalaronne", @@ -4956,499 +3194,44 @@ FR_BASE_EXCEPTIONS = [ "Châtillon-sur-Seiche", "Châtillon-sur-Seine", "Châtillon-sur-Thouet", -"Chatonrupt-Sommermont", "Châtres-la-Forêt", "Châtres-sur-Cher", -"Chatuzange-le-Goubet", -"chauche-branche", -"chauche-branches", -"chauche-poule", -"Chauconin-Neufmontiers", -"Chaudefonds-sur-Layon", -"Chaudenay-la-Ville", -"Chaudenay-le-Château", -"Chaudeney-sur-Moselle", -"Chaudière-Appalaches", -"Chaudon-Norante", -"Chaudron-en-Mauges", -"chauffe-assiette", -"chauffe-assiettes", -"chauffe-bain", -"chauffe-bains", -"chauffe-biberon", -"chauffe-biberons", -"chauffe-bloc", -"chauffe-blocs", -"chauffe-chemise", -"chauffe-cire", -"chauffe-double", -"chauffe-eau", -"chauffe-eaux", -"chauffe-la-couche", -"chauffe-linge", -"chauffe-linges", -"chauffe-lit", -"chauffe-lits", -"chauffe-moteur", -"chauffe-pied", -"chauffe-pieds", -"chauffe-plat", -"chauffe-plats", -"chauffes-doubles", -"Chauffour-lès-Bailly", -"Chauffour-lès-Etréchy", -"Chauffour-lès-Étréchy", -"Chauffour-sur-Vell", -"Chaufour-lès-Bonnières", -"Chaufour-Notre-Dame", -"Chaume-et-Courchamp", -"Chaume-lès-Baigneux", -"Chaumes-en-Brie", -"Chaumes-en-Retz", -"Chaumont-d'Anjou", -"Chaumont-devant-Damvillers", -"Chaumont-en-Vexin", -"Chaumont-Gistoux", -"Chaumont-la-Ville", -"Chaumont-le-Bois", -"Chaumont-le-Bourg", -"Chaumont-Porcien", -"Chaumont-Saint-Quentin", -"Chaumont-sur-Aire", -"Chaumont-sur-Loire", -"Chaumont-sur-Tharonne", -"Chaumoux-Marcilly", -"Chaussée-Notre-Dame-Louvignies", -"chausse-pied", -"chausse-pieds", -"chausse-trape", -"chausse-trapes", -"chausse-trappe", -"chausse-trappes", -"Chaussoy-Epagny", -"Chauvac-Laux-Montaux", -"Chauvency-le-Château", -"Chauvency-Saint-Hubert", -"chauve-souriceau", -"chauve-souricelle", -"chauve-souricière", -"chauve-souricières", -"chauve-souris", -"chauve-souris-garou", -"chauves-souriceaux", -"chauves-souricelles", -"chauves-souris", -"chauves-souris-garous", -"Chauvigny-du-Perche", -"Chauvincourt-Provemont", -"Chauvirey-le-Châtel", -"Chauvirey-le-Vieil", -"chaux-azote", -"chaux-azotes", -"Chaux-Champagny", -"Chaux-de-Fonnier", -"Chaux-des-Crotenay", -"Chaux-des-Prés", -"Chaux-la-Lotière", -"Chaux-lès-Clerval", -"Chaux-lès-Passavant", -"Chaux-lès-Port", -"Chaux-Neuve", -"Chavagnes-en-Paillers", -"Chavagnes-les-Redoux", -"Chavagneux-Montbertand", -"Chavaniac-Lafayette", -"Chavannes-de-Bogis", -"Chavannes-des-Bois", -"Chavannes-le-Chêne", -"Chavannes-les-Grands", -"Chavannes-le-Veyron", -"Chavannes-près-Renens", -"Chavannes-sur-l'Etang", -"Chavannes-sur-l'Étang", -"Chavannes-sur-Moudon", -"Chavannes-sur-Reyssouze", -"Chavannes-sur-Suran", -"Chavigny-Bailleul", -"Chavot-Courcourt", -"Chazay-d'Azergues", -"Chazé-Henry", -"Chazelles-sur-Albe", -"Chazelles-sur-Lavieu", -"Chazelles-sur-Lyon", -"Chazé-sur-Argos", -"Chazey-Bons", -"Chazey-sur-Ain", -"check-up", -"check-ups", -"cheese-cake", -"cheese-cakes", -"chef-boutonnais", -"Chef-Boutonnais", -"chef-boutonnaise", -"Chef-Boutonnaise", -"chef-boutonnaises", -"Chef-Boutonnaises", -"Chef-Boutonne", -"chef-d'oeuvre", -"chef-d'œuvre", -"Chef-du-Pont", -"Cheffreville-Tonnencourt", -"Chef-Haut", -"chef-lieu", -"chef-mets", -"chef-mois", -"chefs-d'oeuvre", -"chefs-d'œuvre", -"chefs-lieux", -"Cheignieu-la-Balme", -"Cheilly-lès-Maranges", -"Chein-Dessus", -"Cheix-en-Retz", -"Chelle-Debat", -"Chelle-Spou", -"Chémeré-le-Roi", "Chémery-Chéhéry", "Chémery-les-Deux", "Chémery-sur-Bar", -"Chemillé-en-Anjou", -"Chemillé-Melay", -"Chemillé-sur-Dême", -"Chemillé-sur-Indrois", -"Chemilly-les-Raves", -"Chemilly-près-Seignelay", -"Chemilly-sur-Serein", -"Chemilly-sur-Yonne", -"Chemin-d'Aisey", -"Chemiré-en-Charnie", -"Chemiré-le-Gaudin", -"Chemiré-sur-Sarthe", -"Chenac-Saint-Seurin-d'Uzet", -"Chenailler-Mascheix", -"Chenay-le-Châtel", +"Chémeré-le-Roi", +"Chérencé-le-Héron", +"Chérencé-le-Roussel", +"Chéry-Chartreuve", +"Chéry-Chartreuvois", +"Chéry-Chartreuvoise", +"Chéry-Chartreuvoises", +"Chéry-lès-Pouilly", +"Chéry-lès-Rozoy", +"Chézery-Forens", +"Chézy-en-Orxois", +"Chézy-sur-Marne", "Chêne-Arnoult", "Chêne-Bernard", "Chêne-Bougeries", "Chêne-Bourg", "Chêne-Carré", -"Chenecey-Buillon", "Chêne-Chenu", "Chêne-Dolley", -"Chêne-en-Semine", -"chêne-gomme", -"Chênehutte-Trèves-Cunault", -"chêne-liège", -"chêne-marin", "Chêne-Pâquier", -"chêne-pommier", "Chêne-Sec", -"chênes-gommes", -"chênes-lièges", -"chênes-marins", -"Chenevrey-et-Morogne", -"Chenillé-Champteussé", -"Chenillé-Changé", -"Chennery-et-Landreville", -"Chennevières-lès-Louvres", -"Chennevières-sur-Marne", -"Chens-sur-Léman", -"Cheppes-la-Prairie", -"chèque-cadeau", -"chèque-repas", -"chèque-restaurant", -"chèques-cadeaux", -"chèques-repas", -"chèques-restaurants", -"chèques-vacances", -"chèque-vacances", -"Cherbourg-en-Cotentin", -"Cherbourg-Octeville", -"cherche-fiche", -"cherche-merde", -"cherche-midi", -"cherche-pointe", -"Chérencé-le-Héron", -"Chérencé-le-Roussel", -"Chermizy-Ailles", -"Cherveix-Cubas", -"Cherves-Châtelars", -"Cherves-Richemont", -"Chéry-Chartreuve", -"chéry-chartreuvois", -"Chéry-Chartreuvois", -"chéry-chartreuvoise", -"Chéry-Chartreuvoise", -"chéry-chartreuvoises", -"Chéry-Chartreuvoises", -"Chéry-lès-Pouilly", -"Chéry-lès-Rozoy", -"Chesalles-sur-Moudon", -"Cheseaux-Noréaz", -"Cheseaux-sur-Lausanne", -"Chesne-Arnoul", -"Chesne-Carré", -"Chesne-Dolley", -"Chesnois-Auboncourt", -"Chessy-les-Prés", -"Chester-le-Street", -"Chevagny-les-Chevrières", -"Chevagny-sur-Guye", -"Chevaigné-du-Maine", -"Cheval-Blanc", -"cheval-fondu", -"cheval-garou", -"cheval-heure", -"cheval-jupon", -"cheval-vapeur", -"Chevannes-Changy", -"chevau-léger", -"chevau-légers", -"chevaux-léger", -"chevaux-légers", -"chevaux-vapeur", -"cheveu-de-Marie-Madeleine", -"cheveux-de-Marie-Madeleine", -"Chevigney-lès-Vercel", -"Chevigney-sur-l'Ognon", -"Chevigny-en-Valière", -"Chevigny-Saint-Sauveur", -"Chevillon-sur-Huillard", -"Chevilly-Larue", -"Cheviré-le-Rouge", -"chèvre-choutiste", -"chèvre-choutistes", -"chèvre-feuille", -"chèvre-pied", -"chèvre-pieds", -"chèvres-feuilles", -"Chevresis-Monceau", -"Chevry-Cossigny", -"Chevry-en-Sereine", -"Chevry-sous-le-Bignon", -"chewing-gum", -"chewing-gums", -"Cheylard-l'Evêque", -"Cheylard-l'Évêque", -"Chezal-Benoît", -"Chézery-Forens", -"chez-moi", -"chez-soi", -"chez-sois", -"Chézy-en-Orxois", -"Chézy-sur-Marne", -"Chibougamo-Chapien", -"chiche-face", -"chiche-kebab", -"chiche-kébab", -"chiches-faces", -"chiches-kebabs", -"chie-en-lit", -"chie-en-lits", -"chien-assis", -"chien-cerf", -"chien-chaud", -"chien-chauds", -"chien-de-mer", -"chien-garou", -"chien-loup", -"chienne-louve", -"chiennes-louves", -"chien-nid", -"chien-rat", -"chiens-assis", -"chiens-cerf", -"chiens-de-mer", -"chiens-garous", -"chiens-loups", -"chiens-nids", -"chiens-rats", -"chiffres-clés", -"chiffres-taxes", -"chiffre-taxe", -"Chigny-les-Roses", -"Chilleurs-aux-Bois", -"Chilly-le-Vignoble", -"Chilly-Mazarin", -"Chilly-sur-Salins", -"china-paya", -"Chiopris-Viscone", -"chiotte-kès", -"chiottes-kès", -"Chirac-Bellevue", -"Chirat-l'Eglise", -"Chirat-l'Église", -"Chiré-en-Montreuil", -"chirurgien-dentiste", -"chirurgiens-dentistes", -"Chiry-Ourscamp", -"Chiry-Ourscamps", -"Chissay-en-Touraine", -"Chissey-en-Morvan", -"Chissey-lès-Mâcon", -"Chissey-sur-Loue", -"Chitry-les-Mines", -"Chivres-en-Laonnois", -"Chivres-Val", -"Chivy-lès-Etouvelles", -"Chivy-lès-Étouvelles", -"ch'kâra", -"ch'kâras", -"ch.-l.", -"chloro-IPC", -"chlorpyriphos-éthyl", -"chlorpyriphos-méthyl", -"ch'ni", -"choano-organismes", -"choche-pierre", -"choche-poule", -"Choilley-Dardenay", -"Choisy-au-Bac", -"Choisy-en-Brie", -"Choisy-la-Victoire", -"Choisy-le-Roi", -"Choloy-Ménillot", -"Chonas-l'Amballan", -"Chonville-Malaumont", -"Choqueuse-les-Bénards", -"Chorey-les-Beaune", -"choux-choux", -"choux-fleurs", -"choux-navets", -"choux-palmistes", -"choux-raves", -"Chouzé-sur-Loire", -"Chouzy-sur-Cisse", -"chow-chow", -"chow-chows", -"chrétiens-démocrates", -"christe-marine", -"christes-marines", -"chrom-brugnatellite", -"chrom-brugnatellites", -"chrome-clinozoïsite", -"chrome-clinozoïsites", -"chrome-fluorite", -"chrome-fluorites", -"chrome-pistazite", -"chrome-pistazites", -"chrome-trémolite", -"chrome-trémolites", -"chrome-zoïsite", -"chrome-zoïsites", -"chrono-localisation", -"chrono-localisations", -"ch't'aime", -"ch'ti", -"ch'tiisa", -"ch'tiisai", -"ch'tiisaient", -"ch'tiisais", -"ch'tiisait", -"ch'tiisâmes", -"ch'tiisant", -"ch'tiisas", -"ch'tiisasse", -"ch'tiisassent", -"ch'tiisasses", -"ch'tiisassiez", -"ch'tiisassions", -"ch'tiisât", -"ch'tiisâtes", -"ch'tiise", -"ch'tiisé", -"ch'tiisée", -"ch'tiisées", -"ch'tiisent", -"ch'tiiser", -"ch'tiisera", -"ch'tiiserai", -"ch'tiiseraient", -"ch'tiiserais", -"ch'tiiserait", -"ch'tiiseras", -"ch'tiisèrent", -"ch'tiiserez", -"ch'tiiseriez", -"ch'tiiserions", -"ch'tiiserons", -"ch'tiiseront", -"ch'tiises", -"ch'tiisés", -"ch'tiisez", -"ch'tiisiez", -"ch'tiisions", -"ch'tiisons", -"ch'timi", -"ch'tis", -"Chuffilly-Roche", -"chuteur-op", -"chuteurs-ops", -"cia-cia", -"ci-après", -"ci-attaché", -"ci-contre", -"ci-delez", -"ci-dessous", -"ci-dessus", -"ci-devant", +"Chêne-en-Semine", +"Chênehutte-Trèves-Cunault", "Cier-de-Luchon", "Cier-de-Rivière", "Cierges-sous-Montfaucon", "Cierp-Gaud", -"ci-gisent", -"ci-git", -"ci-gît", -"ci-haut", -"ci-hauts", -"ci-incluse", -"ci-incluses", -"ci-joint", -"ci-jointe", -"ci-jointes", -"ci-joints", -"ciné-club", -"ciné-clubs", -"cinéma-dinatoire", -"cinéma-dinatoires", -"ciné-parc", -"cinq-cents", -"cinq-dix-quinze", -"cinq-huitième", -"cinq-marsien", -"Cinq-Marsien", -"cinq-marsienne", -"Cinq-Marsienne", -"cinq-marsiennes", -"Cinq-Marsiennes", -"cinq-marsiens", -"Cinq-Marsiens", "Cinq-Mars-la-Pile", -"cinq-mâts", -"cinq-quatre-un", -"cinq-six", -"cinquante-cinq", -"cinquante-cinquante", -"cinquante-deux", -"cinquante-et-un", -"cinquante-et-une", -"cinquante-et-unième", -"cinquante-et-unièmes", -"cinquante-huit", -"cinquante-neuf", -"cinquante-quatre", -"cinquante-sept", -"cinquante-six", -"cinquante-trois", -"ci-plus-bas", -"ci-plus-haut", -"circolo-mezzo", -"circonscriptions-clés", +"Cinq-Marsien", +"Cinq-Marsienne", +"Cinq-Marsiennes", +"Cinq-Marsiens", "Circourt-sur-Mouzon", -"circum-aural", -"circum-continental", -"Ciré-d'Aunis", -"cire-pompe", -"cire-pompes", "Cires-lès-Mello", "Cirey-lès-Mareilles", "Cirey-lès-Pontailler", @@ -5456,25 +3239,12 @@ FR_BASE_EXCEPTIONS = [ "Cirey-sur-Vezouze", "Cirfontaines-en-Azois", "Cirfontaines-en-Ornois", -"cirque-ménagerie", -"cirques-ménageries", -"cirques-théâtres", -"cirque-théâtre", -"Ciry-le-Noble", "Ciry-Salsogne", +"Ciry-le-Noble", +"Ciré-d'Aunis", "Cisai-Saint-Aubin", -"cis-gangétique", -"cis-gangétiques", "Cissac-Médoc", "Cisternes-la-Forêt", -"cis-verbénol", -"cité-dortoir", -"cité-État", -"cités-dortoirs", -"cités-États", -"citizen-band", -"citron-pays", -"citrons-pays", "Civrac-de-Blaye", "Civrac-de-Dordogne", "Civrac-en-Médoc", @@ -5487,141 +3257,74 @@ FR_BASE_EXCEPTIONS = [ "Civry-la-Forêt", "Civry-sur-Serein", "Cizay-la-Madeleine", -"clac-clac", -"clac-clacs", "Clacton-on-Sea", "Clacy-et-Thierret", "Clairefontaine-en-Yvelines", "Clairvaux-d'Aveyron", "Clairvaux-les-Lacs", "Clairy-Saulchoix", -"claque-merde", -"claque-oreille", -"claque-oreilles", -"claque-patin", -"claque-patins", "Clarafond-Arcine", "Clausthal-Zellerfeld", "Clavans-en-Haut-Oisans", -"clavi-cylindre", -"clavi-harpe", "Claville-Motteville", -"clavi-lyre", "Clavy-Warby", "Claye-Souilly", -"Cléden-Cap-Sizun", -"Cléden-Poher", -"clématites-viornes", -"clématite-viorne", -"Clémence-d'Ambel", -"Cléon-d'Andran", -"Cléré-du-Bois", -"Cléré-les-Pins", -"Cléré-sur-Layon", -"Clérey-la-Côte", -"Clérey-sur-Brenon", -"clérico-nationaliste", -"clérico-nationalistes", +"Clef Vallée d'Eure", "Clermont-Créans", -"Clermont-de-Beauregard", "Clermont-Dessous", "Clermont-Dessus", -"Clermont-d'Excideuil", -"Clermont-en-Argonne", "Clermont-Ferrand", -"Clermont-le-Fort", -"Clermont-les-Fermes", -"Clermont-l'Hérault", "Clermont-Pouyguillès", "Clermont-Savès", "Clermont-Soubiran", +"Clermont-d'Excideuil", +"Clermont-de-Beauregard", +"Clermont-en-Argonne", +"Clermont-l'Hérault", +"Clermont-le-Fort", +"Clermont-les-Fermes", "Clermont-sous-Huy", "Clermont-sur-Lauquet", -"Cléry-en-Vexin", -"Cléry-Grand", -"Cléry-le-Grand", -"Cléry-le-Petit", -"Cléry-Petit", -"Cléry-Saint-André", -"Cléry-sur-Somme", -"clic-clac", "Clichy-sous-Bois", -"client-cible", -"client-cibles", -"client-serveur", -"cligne-musette", -"climato-sceptique", -"climato-sceptiques", "Clinchamps-sur-Orne", -"clin-foc", -"clin-focs", -"cloche-pied", -"cloche-pieds", -"cloche-plaque", -"clodinafop-propargyl", "Clohars-Carnoët", "Clohars-Fouesnant", "Clonas-sur-Varèze", -"clopin-clopant", -"cloquintocet-mexyl", "Clos-Fontaine", -"clos-fontainois", "Clos-Fontainois", -"clos-fontainoise", "Clos-Fontainoise", -"clos-fontainoises", "Clos-Fontainoises", -"clos-masure", -"clos-masures", -"clos-vougeot", -"clos-vougeots", "Cloyes-les-Trois-Rivières", -"Cloyes-sur-le-Loir", "Cloyes-sur-Marne", -"club-house", -"clubs-houses", +"Cloyes-sur-le-Loir", "Cluj-Napoca", "Clun's", "Clussais-la-Pommeraie", "Clux-Villeneuve", "Cluze-et-Pâquier", +"Cléden-Cap-Sizun", +"Cléden-Poher", +"Clémence-d'Ambel", +"Cléon-d'Andran", +"Clérey-la-Côte", +"Clérey-sur-Brenon", +"Cléry-Grand", +"Cléry-Petit", +"Cléry-Saint-André", +"Cléry-en-Vexin", +"Cléry-le-Grand", +"Cléry-le-Petit", +"Cléry-sur-Somme", +"Cléré-du-Bois", +"Cléré-les-Pins", +"Cléré-sur-Layon", "Coat-Méal", -"coat-méalien", "Coat-Méalien", -"coat-méalienne", "Coat-Méalienne", -"coat-méaliennes", "Coat-Méaliennes", -"coat-méaliens", "Coat-Méaliens", -"cobalt-gris", -"cobalt-mica", -"cobalt-ochre", -"cobalto-épsomite", -"cobalto-épsomites", -"cobalto-sphaérosidérite", -"cobalto-sphaérosidérites", -"cobalts-gris", -"cobalts-micas", -"cobalts-ochres", "Cochem-Zell", -"cochon-garou", -"cochons-garous", -"coco-de-mer", -"coco-fesses", -"cocotte-minute", "Cocquio-Trevisago", -"codes-barres", -"codes-clés", -"cœur-de-Jeannette", -"coeur-de-pigeon", -"cœur-de-pigeon", -"coeurs-de-pigeon", -"coeurs-de-pigeons", -"cœurs-de-pigeons", -"Cœuvres-et-Valsery", -"coffre-fort", -"coffres-forts", "Cognac-la-Forêt", "Cognac-le-Froid", "Cognat-Lyonne", @@ -5629,23 +3332,12 @@ FR_BASE_EXCEPTIONS = [ "Cognocoli-Monticchi", "Coiffy-le-Bas", "Coiffy-le-Haut", -"coin-coin", -"coin-coins", "Coin-lès-Cuvry", "Coin-sur-Seille", "Coise-Saint-Jean-Pied-Gauthier", "Coizard-Joches", "Colayrac-Saint-Cirq", -"colin-maillard", -"colin-tampon", -"colis-route", -"colis-routes", "Collandres-Quincarnon", -"collant-pipette", -"collant-pipettes", -"collé-serré", -"collés-serrés", -"collet-monté", "Colleville-Montgomery", "Colleville-sur-Mer", "Colleville-sur-Orne", @@ -5653,8 +3345,6 @@ FR_BASE_EXCEPTIONS = [ "Colligis-Crandelain", "Colligny-Maizery", "Colline-Beaumont", -"colloid-calcite", -"colloid-calcites", "Collombey-Muraz", "Collonge-Bellerive", "Collonge-en-Charollais", @@ -5668,100 +3358,75 @@ FR_BASE_EXCEPTIONS = [ "Colmesnil-Manneville", "Colmier-le-Bas", "Colmier-le-Haut", -"col-nu", -"Colombé-la-Fosse", "Colombe-lès-Bithaine", -"Colombé-le-Sec", "Colombe-lès-Vesoul", "Colombey-les-Belles", -"Colombey-lès-Choiseul", "Colombey-les-Deux-Eglises", "Colombey-les-Deux-Églises", +"Colombey-lès-Choiseul", "Colombie-Anglaise", "Colombie-Britannique", "Colombier-Châtelot", -"Colombier-en-Brionnais", -"Colombières-sur-Orb", "Colombier-Fontaine", +"Colombier-Saugnieu", +"Colombier-en-Brionnais", "Colombier-le-Cardinal", "Colombier-le-Jeune", "Colombier-le-Vieux", -"Colombier-Saugnieu", "Colombiers-du-Plessis", "Colombiers-sur-Seulles", +"Colombières-sur-Orb", "Colomby-Anguerny", "Colomby-sur-Thaon", +"Colombé-la-Fosse", +"Colombé-le-Sec", "Colonard-Corubert", "Colpach-Bas", "Colpach-Haut", "Colroy-la-Grande", "Colroy-la-Roche", -"cols-nus", -"cols-verts", -"col-vert", -"col-verts", -"colville-okanagan", "Comberanche-et-Epeluche", "Comberanche-et-Épeluche", -"combi-short", -"combi-shorts", -"Comblain-au-Pont", "Comblain-Fairon", -"comble-lacune", -"comble-lacunes", +"Comblain-au-Pont", "Combles-en-Barrois", "Combres-sous-les-Côtes", "Combs-la-Ville", -"com'com", -"come-back", -"comédie-ballet", -"comédies-ballets", "Comezzano-Cizzago", -"Comines-Warneton", "Comin-Yanga", +"Comines-Warneton", "Commelle-Vernay", -"commissaire-priseur", -"commissaires-priseurs", -"commis-voyageur", -"commis-voyageurs", "Communailles-en-Montagne", -"compère-loriot", -"compères-loriot", -"compositeur-typographe", -"compositeur-typographes", "Comps-la-Grand-Ville", "Comps-sur-Artuby", -"comptes-rendus", -"concavo-concave", -"concavo-convexe", "Conches-en-Ouche", "Conches-sur-Gondoire", "Conchez-de-Béarn", "Conchil-le-Temple", "Conchy-les-Pots", "Conchy-sur-Canche", -"Concœur-et-Corboin", "Concourson-sur-Layon", +"Concœur-et-Corboin", "Condat-en-Combraille", "Condat-lès-Montboissier", "Condat-sur-Ganaveix", "Condat-sur-Trincou", -"Condat-sur-Vézère", "Condat-sur-Vienne", +"Condat-sur-Vézère", +"Condeixa-a-Nova", +"Condom-d'Aubrac", +"Condé-Folie", +"Condé-Northen", +"Condé-Sainte-Libiaire", "Condé-en-Brie", "Condé-en-Normandie", -"Condé-Folie", -"Condeixa-a-Nova", "Condé-lès-Autry", "Condé-lès-Herpy", "Condé-lès-Vouziers", -"Condé-Northen", -"Condé-Sainte-Libiaire", "Condé-sur-Aisne", "Condé-sur-Huisne", "Condé-sur-Ifs", "Condé-sur-Iton", -"Condé-sur-l'Escaut", "Condé-sur-Marne", "Condé-sur-Noireau", "Condé-sur-Risle", @@ -5770,165 +3435,85 @@ FR_BASE_EXCEPTIONS = [ "Condé-sur-Suippe", "Condé-sur-Vesgre", "Condé-sur-Vire", -"Condom-d'Aubrac", -"conférences-débats", -"Conflans-en-Jarnisy", +"Condé-sur-l'Escaut", "Conflans-Sainte-Honorine", +"Conflans-en-Jarnisy", "Conflans-sur-Anille", "Conflans-sur-Lanterne", "Conflans-sur-Loing", "Conflans-sur-Seine", "Confolent-Port-Dieu", -"conforte-main", "Confort-Meilars", "Congerville-Thionville", -"Congé-sur-Orne", "Congis-sur-Thérouanne", "Congo-Brazzaville", -"congo-kinois", "Congo-Kinshasa", "Congo-Léo", "Congo-Léopoldville", -"congolo-kinois", -"congolo-kinoise", -"congolo-kinoises", +"Congé-sur-Orne", "Conie-Molitard", "Conilhac-Corbières", "Conilhac-de-la-Montagne", "Connantray-Vaurefroy", -"Conne-de-la-Barde", "Conne-de-Labarde", +"Conne-de-la-Barde", "Conques-en-Rouergue", "Conques-sur-Orbiel", -"conseil-général", +"Cons-Sainte-Colombe", "Cons-la-Grandville", "Consolation-Maisonnettes", -"Cons-Sainte-Colombe", "Contamine-Sarzin", "Contamine-sur-Arve", "Conteville-en-Ternois", "Conteville-lès-Boulogne", -"contra-latéral", -"contrat-cadre", -"contrats-cadres", "Contres-en-Vairais", -"contrôle-commande", "Contz-les-Bains", -"convexo-concave", -"copiable-collable", -"copiables-collables", -"copia-colla", -"copiage-collage", -"copiages-collages", -"copiai-collai", -"copiaient-collaient", -"copiais-collais", -"copiait-collait", -"copiâmes-collâmes", -"copiant-collant", -"copias-collas", -"copiasse-collasse", -"copiassent-collassent", -"copiasses-collasses", -"copiassiez-collassiez", -"copiassions-collassions", -"copiât-collât", -"copiâtes-collâtes", -"copie-colle", -"copié-collé", -"copié-collés", -"copiée-collée", -"copiées-collées", -"copie-lettres", -"copient-collent", -"copiera-collera", -"copierai-collerai", -"copieraient-colleraient", -"copierais-collerais", -"copierait-collerait", -"copieras-colleras", -"copier-coller", -"copier-collers", -"copièrent-collèrent", -"copierez-collerez", -"copieriez-colleriez", -"copierions-collerions", -"copierons-collerons", -"copieront-colleront", -"copies-colles", -"copiés-collés", -"copiez-collez", -"copiez-colliez", -"copions-collions", -"copions-collons", -"coq-à-l'âne", -"coq-de-roche", -"coq-héron", -"coqs-de-roche", -"coq-souris", -"coquel'œil", -"coquel'œils", -"coral-rag", -"corbeau-pêcheur", -"corbeaux-pêcheurs", "Corbeil-Cerf", "Corbeil-Essonnes", -"corbeil-essonnois", "Corbeil-Essonnois", -"corbeil-essonnoise", "Corbeil-Essonnoise", -"corbeil-essonnoises", "Corbeil-Essonnoises", "Corbère-Abères", "Corbère-les-Cabanes", "Corcelle-Mieslot", "Corcelles-Cormondrèche", -"Corcelles-en-Beaujolais", "Corcelles-Ferrières", +"Corcelles-en-Beaujolais", "Corcelles-le-Jorat", "Corcelles-les-Arts", -"Corcelles-lès-Cîteaux", "Corcelles-les-Monts", +"Corcelles-lès-Cîteaux", "Corcelles-près-Concise", "Corcelles-près-Payerne", "Corcelles-sur-Chavornay", "Corcoué-sur-Logne", -"Cordes-sur-Ciel", "Cordes-Tolosannes", -"cordons-bleus", -"Corée-du-Nord", -"Corée-du-Sud", +"Cordes-sur-Ciel", "Corgnac-sur-l'Isle", "Cormaranche-en-Bugey", "Corme-Ecluse", +"Corme-Royal", "Corme-Écluse", "Cormeilles-en-Parisis", "Cormeilles-en-Vexin", "Cormelles-le-Royal", -"Corme-Royal", "Cormoranche-sur-Saône", -"Cormot-le-Grand", "Cormot-Vauchignon", -"corned-beef", -"corned-beefs", +"Cormot-le-Grand", "Corneilla-de-Conflent", "Corneilla-del-Vercol", "Corneilla-la-Rivière", "Corneville-la-Fouquetière", "Corneville-sur-Risle", -"corn-flake", -"corn-flakes", -"Cornillé-les-Caves", "Cornillon-Confoux", "Cornillon-en-Trièves", "Cornillon-sur-l'Oule", -"Corny-la-Ville", +"Cornillé-les-Caves", "Corny-Machéroménil", +"Corny-la-Ville", "Corny-sur-Moselle", "Corpataux-Magnedens", "Corpoyer-la-Chapelle", -"corps-mort", -"corps-morts", "Corps-Nuds", "Corral-Rubio", "Corrençon-en-Vercors", @@ -5936,17 +3521,14 @@ FR_BASE_EXCEPTIONS = [ "Corroy-le-Grand", "Corse-du-Sud", "Corsier-sur-Vevey", -"cortico-cortical", -"cortico-corticale", -"cortico-corticales", -"cortico-corticaux", "Cortil-Noirmont", -"cortil-noirmontois", "Cortil-Noirmontois", "Cortil-Noirmontoise", "Cortil-Wodon", "Corvol-d'Embernard", "Corvol-l'Orgueilleux", +"Corée-du-Nord", +"Corée-du-Sud", "Coslédaà-Lube-Boast", "Cosne-Cours-sur-Loire", "Cosne-d'Allier", @@ -5954,119 +3536,71 @@ FR_BASE_EXCEPTIONS = [ "Cossé-d'Anjou", "Cossé-en-Champagne", "Cossé-le-Vivien", -"costard-cravate", -"costards-cravates", "Costa-Rica", "Costa-Ricain", -"costa-ricien", "Costa-Ricien", -"costa-ricienne", "Costa-Ricienne", -"costa-riciennes", "Costa-Riciennes", -"costa-riciens", "Costa-Riciens", -"costo-claviculaire", -"costo-sternal", -"costo-thoracique", -"costo-vertébral", -"costo-vertébrale", -"costo-vertébrales", -"costo-vertébraux", -"cosy-corner", -"cosy-corners", "Coteau-Landais", "Coteau-Libre", "Coteaux-du-Lizon", -"Côtes-d'Armor", -"côtes-de-toul", -"Côtes-du-Nord", -"côtes-du-rhône", -"côtes-du-Rhône", -"côtes-du-rhônes", "Coti-Chiavari", -"coton-poudre", -"coton-poudres", -"cotons-poudres", -"cotons-tiges", -"coton-tige", -"cotte-hardie", -"cottes-hardies", -"couble-soiffière", -"couche-culotte", -"couche-point", -"couche-points", -"couches-culottes", "Couches-les-Mines", -"couche-tard", -"couche-tôt", -"couci-couça", -"couci-couci", "Coucy-la-Ville", "Coucy-le-Château", "Coucy-le-Château-Auffrique", -"Coucy-lès-Eppes", "Coucy-les-Saints", -"coude-à-coude", -"cou-de-jatte", +"Coucy-lès-Eppes", "Coudekerque-Branche", -"Coudekerque-sur-le-Rhin", "Coudekerque-Village", -"cou-de-pied", -"coude-pied", +"Coudekerque-sur-le-Rhin", "Coudeville-sur-Mer", -"Coudray-au-Perche", "Coudray-Rabut", +"Coudray-au-Perche", "Couesmes-Vaucé", "Couffy-sur-Sarsonne", "Couilly-Pont-aux-Dames", -"cou-jaune", "Coulanges-la-Vineuse", "Coulanges-lès-Nevers", "Coulanges-sur-Yonne", "Coulans-sur-Gée", "Coulans-sur-Lizon", -"coule-sang", "Coulmier-le-Sec", "Coulombs-en-Valois", "Coulommes-et-Marqueny", "Coulommes-la-Montagne", "Coulommes-lès-Attigny", "Coulommiers-la-Tour", -"Coulonges-Cohan", -"Coulonges-les-Sablons", -"Coulonges-sur-l'Autize", -"Coulonges-sur-Sarthe", -"Coulonges-Thouarsais", "Coulonge-sur-Charente", +"Coulonges-Cohan", +"Coulonges-Thouarsais", +"Coulonges-les-Sablons", +"Coulonges-sur-Sarthe", +"Coulonges-sur-l'Autize", "Couloumé-Mondebat", "Coulounieix-Chamiers", "Coulouvray-Boisbenâtre", -"cou-nu", -"coupé-cabriolet", -"coupé-collé", -"coupé-décalé", -"coupé-lit", "Coupelle-Neuve", "Coupelle-Vieille", -"couper-coller", -"coupés-cabriolets", -"coupés-collés", -"coupés-décalés", -"coupés-lits", -"coupon-réponse", -"coupons-réponses", -"coups-de-poing", -"courant-jet", -"courants-jets", -"Courcelles-au-Bois", +"Cour-Cheverny", +"Cour-Maugis sur Huisne", +"Cour-Saint-Maurice", +"Cour-et-Buis", +"Cour-l'Evêque", +"Cour-l'Évêque", +"Cour-sur-Heure", +"Cour-sur-Loire", "Courcelles-Chaussy", +"Courcelles-Epayelles", +"Courcelles-Frémoy", +"Courcelles-Sapicourt", +"Courcelles-Val-d'Esnoms", +"Courcelles-au-Bois", "Courcelles-de-Touraine", "Courcelles-en-Barrois", "Courcelles-en-Bassée", "Courcelles-en-Montagne", -"Courcelles-Epayelles", -"Courcelles-Frémoy", "Courcelles-la-Forêt", "Courcelles-le-Comte", "Courcelles-lès-Châtillon", @@ -6075,7 +3609,6 @@ FR_BASE_EXCEPTIONS = [ "Courcelles-lès-Montbard", "Courcelles-lès-Montbéliard", "Courcelles-lès-Semur", -"Courcelles-Sapicourt", "Courcelles-sous-Châtenois", "Courcelles-sous-Moyencourt", "Courcelles-sous-Thoix", @@ -6088,54 +3621,24 @@ FR_BASE_EXCEPTIONS = [ "Courcelles-sur-Vesles", "Courcelles-sur-Viosne", "Courcelles-sur-Voire", -"Courcelles-Val-d'Esnoms", -"Cour-Cheverny", "Courcy-aux-Loges", "Courdimanche-sur-Essonne", -"Cour-et-Buis", -"coure-vite", -"Cour-l'Evêque", -"Cour-l'Évêque", "Courlon-sur-Yonne", -"cour-masure", "Cournon-d'Auvergne", -"Cour-Saint-Maurice", -"Coursan-en-Othe", "Cours-de-Monségur", "Cours-de-Pile", -"cours-de-pilois", "Cours-de-Pilois", -"cours-de-piloise", "Cours-de-Piloise", -"cours-de-piloises", "Cours-de-Piloises", -"course-poursuite", -"courses-poursuites", -"Courseulles-sur-Mer", "Cours-la-Ville", "Cours-les-Bains", "Cours-les-Barres", -"Courson-les-Carrières", +"Coursan-en-Othe", +"Courseulles-sur-Mer", "Courson-Monteloup", -"Cour-sur-Heure", -"Cour-sur-Loire", -"courte-botte", -"courte-épée", -"courte-épine", -"courte-épines", -"courte-graisse", -"courte-lettre", +"Courson-les-Carrières", "Courtemont-Varennes", -"courte-pointe", -"courte-pointier", -"courte-queue", -"courtes-bottes", -"courtes-épées", -"courtes-lettres", "Courtesoult-et-Gatey", -"courtes-pattes", -"courtes-pointes", -"courtes-queues", "Courtetain-et-Salans", "Courtine-le-Trucq", "Courtois-sur-Yonne", @@ -6144,483 +3647,202 @@ FR_BASE_EXCEPTIONS = [ "Courtonne-les-Deux-Églises", "Courtrai-Dutsel", "Courtrizy-et-Fussigny", -"courts-bandages", -"courts-boutons", -"courts-circuits", -"courts-côtés", -"courts-cureaux", -"courts-jus", -"courts-métrages", -"courts-tours", "Courville-sur-Eure", "Cousances-au-Bois", "Cousances-les-Forges", "Cousances-lès-Triconville", -"cous-cous", -"cous-de-jatte", -"cous-de-pied", -"cous-jaunes", "Coussac-Bonneval", "Coussay-les-Bois", -"cout'donc", -"couteau-de-chasse", -"couteau-scie", -"couteaux-de-chasse", -"couteaux-scie", "Couthures-sur-Garonne", -"Couture-d'Argenson", "Couture-Saint-Germain", +"Couture-d'Argenson", "Couture-sur-Loir", -"couvre-casque", -"couvre-casques", -"couvre-chaussure", -"couvre-chaussures", -"couvre-chef", -"couvre-chefs", -"couvre-clef", -"couvre-clefs", -"couvre-face", -"couvre-faces", -"couvre-feu", -"couvre-feux", -"couvre-giberne", -"couvre-gibernes", -"couvre-joint", -"couvre-joints", -"couvre-lit", -"couvre-lits", -"couvre-livre", -"couvre-livres", -"couvre-lumière", -"couvre-lumières", -"couvre-manche", -"couvre-manches", -"couvre-nuque", -"couvre-nuques", -"couvre-objet", -"couvre-objets", -"couvre-orteil", -"couvre-orteils", -"couvre-pied", -"couvre-pieds", -"couvre-plat", -"couvre-plats", -"couvre-shako", -"couvre-shakos", -"couvre-sol", -"couvre-sols", -"couvreur-zingueur", "Couvron-et-Aumencourt", +"Coux et Bigaroque-Mouzens", "Coux-et-Bigaroque", "Couze-et-Saint-Front", "Couzon-au-Mont-d'Or", "Couzon-sur-Coulange", -"cover-girl", -"cover-girls", -"cow-boy", -"cow-boys", -"coxa-retrorsa", -"coxo-fémoral", "Coye-la-Forêt", -"c'que", -"c'qui", -"crabe-araignée", -"crabes-araignées", -"crac-crac", -"crachouillot-thérapeute", -"craignant-Dieu", -"Crandelain-et-Malval", -"cran-gevrien", "Cran-Gevrien", -"cran-gevrienne", "Cran-Gevrienne", -"cran-gevriennes", "Cran-Gevriennes", -"cran-gevriens", "Cran-Gevriens", "Cran-Gevrier", -"cranio-facial", +"Crandelain-et-Malval", "Crannes-en-Champagne", "Crans-près-Céligny", "Cranves-Sales", -"cranves-salien", "Cranves-Salien", -"cranves-saliène", -"Cranves-Saliène", -"cranves-saliènes", -"Cranves-Saliènes", -"cranves-saliens", "Cranves-Saliens", -"crapaud-buffle", -"crapauds-buffles", -"crapet-soleil", +"Cranves-Saliène", +"Cranves-Saliènes", "Craponne-sur-Arzon", "Cras-Avernas", "Cras-sur-Reyssouze", "Crasville-la-Mallet", "Crasville-la-Rocquefort", "Cravant-les-Côteaux", -"crayon-feutre", -"crayons-feutre", -"crayons-feutres", -"crayon-souris", -"créateur-typographe", -"Crécey-sur-Tille", -"Crêches-sur-Saône", -"Crécy-au-Mont", -"Crécy-Couvé", -"Crécy-en-Ponthieu", -"Crécy-la-Chapelle", -"Crécy-sur-Serre", -"crédit-bail", -"crédits-bail", -"crédits-bails", -"crédits-baux", -"crédits-temps", -"crédit-temps", -"Crégy-lès-Meaux", "Crempigny-Bonneguête", "Creney-près-Troyes", "Crennes-sur-Fraubée", -"Créon-d'Armagnac", -"Crépieux-la-Pape", -"Crépy-en-Laonnois", -"Crépy-en-Valois", "Crespy-le-Neuf", "Cressac-Saint-Genis", "Cressin-Rochefort", "Cressy-Omencourt", "Cressy-sur-Somme", "Crest-Voland", -"crest-volantain", "Crest-Volantain", -"crest-volantaine", "Crest-Volantaine", -"crest-volantaines", "Crest-Volantaines", -"crest-volantains", "Crest-Volantains", -"Cré-sur-Loir", -"crête-de-coq", -"crête-marine", -"crêtes-de-coq", -"crêtes-marines", "Creutzwald-la-Croix", "Creuzier-le-Neuf", "Creuzier-le-Vieux", "Crevans-et-la-Chapelle-lès-Granges", "Crevant-Laveine", -"crève-chassis", -"crève-chien", -"crève-chiens", -"crève-coeur", -"crève-cœur", -"Crèvecoeur-en-Auge", -"Crèvecœur-en-Auge", -"Crèvecœur-en-Brie", -"Crèvecœur-le-Grand", -"Crèvecœur-le-Petit", -"crève-coeurs", -"crève-cœurs", -"Crèvecoeur-sur-l'Escaut", -"Crèvecœur-sur-l'Escaut", -"crève-la-dalle", -"crève-la-faim", -"crevette-mante", -"crevettes-mantes", -"crève-vessie", -"crève-vessies", "Creys-Mépieu", "Creyssensac-et-Pissot", -"Crézançay-sur-Cher", -"Crézancy-en-Sancerre", -"cric-crac", -"crico-trachéal", -"crico-trachéale", -"crico-trachéales", -"crico-trachéaux", "Cricqueville-en-Auge", "Cricqueville-en-Bessin", -"cri-cri", -"cri-cris", "Criel-sur-Mer", "Crillon-le-Brave", "Criquebeuf-en-Caux", "Criquebeuf-la-Campagne", "Criquebeuf-sur-Seine", -"Criquetot-le-Mauconduit", "Criquetot-l'Esneval", +"Criquetot-le-Mauconduit", "Criquetot-sur-Longueville", "Criquetot-sur-Ouville", "Crissay-sur-Manse", -"cristallo-électrique", -"cristallo-électriques", -"criste-marine", "Criteuil-la-Magdeleine", -"croad-langshan", -"croc-en-jambe", -"crocs-en-jambe", -"croiseur-école", -"croiseurs-écoles", +"Cro-Magnon", +"Cro-Magnons", "Croissy-Beaubourg", "Croissy-sur-Celle", "Croissy-sur-Seine", "Croisy-sur-Andelle", "Croisy-sur-Eure", -"croix-caluois", "Croix-Caluois", -"croix-caluoise", "Croix-Caluoise", -"croix-caluoises", "Croix-Caluoises", "Croix-Caluyau", "Croix-Chapeau", -"croix-de-feu", -"croix-de-Malte", -"Croix-de-Vie", -"Croix-en-Ternois", "Croix-Fonsomme", "Croix-Fonsommes", -"Croix-lez-Rouveroy", "Croix-Mare", "Croix-Moligneaux", -"croix-pile", "Croix-Rousse", -"croix-roussien", -"Croix-roussien", -"croix-roussienne", -"Croix-roussienne", -"croix-roussiennes", -"Croix-roussiennes", -"croix-roussiens", -"Croix-roussiens", "Croix-Valmer", +"Croix-de-Vie", +"Croix-en-Ternois", +"Croix-lez-Rouveroy", +"Croix-roussien", +"Croix-roussienne", +"Croix-roussiennes", +"Croix-roussiens", "Croizet-sur-Gand", -"Cro-Magnon", -"Cro-Magnons", -"cromlec'h", -"cromlec'hs", -"croque-abeilles", -"croque-au-sel", -"croque-en-bouche", -"croque-lardon", -"croque-lardons", -"croque-madame", -"croque-madames", -"croque-mademoiselle", -"croque-mademoiselles", -"croque-messieurs", -"croque-mitaine", -"croque-mitaines", -"croque-monsieur", -"croque-monsieurs", -"croque-mort", -"croque-morts", -"croque-moutons", -"croque-noisette", -"croque-noisettes", -"croque-noix", -"croque-note", "Cros-de-Géorand", "Cros-de-Montvert", "Cros-de-Ronesque", "Crosey-le-Grand", "Crosey-le-Petit", -"crossing-over", "Crosville-la-Vieille", "Crosville-sur-Douve", "Crosville-sur-Scie", -"crotte-du-diable", -"crotte-du-Diable", -"crottes-du-diable", -"crottes-du-Diable", "Crottes-en-Pithiverais", "Crouttes-sur-Marne", -"Crouy-en-Thelle", "Crouy-Saint-Pierre", +"Crouy-en-Thelle", "Crouy-sur-Cosson", "Crouy-sur-Ourcq", "Crouzet-Migette", -"crown-glass", "Crozes-Hermitage", "Crozon-sur-Vauvre", "Crucey-Villages", -"cruci-capétien", "Cruci-Capétien", -"cruci-capétienne", "Cruci-Capétienne", -"cruci-capétiennes", "Cruci-Capétiennes", -"cruci-capétiens", "Cruci-Capétiens", -"cruci-falgardien", "Cruci-Falgardien", -"cruci-falgardienne", "Cruci-Falgardienne", -"cruci-falgardiennes", "Cruci-Falgardiennes", -"cruci-falgardiens", "Cruci-Falgardiens", -"crud-ammoniac", "Cruquius-Oost", "Cruviers-Lascours", "Crux-la-Ville", "Cruzilles-lès-Mépillat", "Cruzy-le-Châtel", -"crypto-communiste", -"crypto-luthérien", -"crypto-luthérienne", -"crypto-luthériennes", -"crypto-luthériens", -"crypto-monnaie", -"crypto-monnaies", -"c'te", +"Crèvecoeur-en-Auge", +"Crèvecoeur-sur-l'Escaut", +"Crèvecœur-en-Auge", +"Crèvecœur-en-Brie", +"Crèvecœur-le-Grand", +"Crèvecœur-le-Petit", +"Crèvecœur-sur-l'Escaut", +"Crèvecœur-en-Auge", +"Crèvecœur-en-Brie", +"Crèvecœur-le-Grand", +"Crèvecœur-le-Petit", +"Crèvecœur-sur-l'Escaut", +"Cré-sur-Loir", +"Crécey-sur-Tille", +"Crécy-Couvé", +"Crécy-au-Mont", +"Crécy-en-Ponthieu", +"Crécy-la-Chapelle", +"Crécy-sur-Serre", +"Crégy-lès-Meaux", +"Créon-d'Armagnac", +"Crépieux-la-Pape", +"Crépy-en-Laonnois", +"Crépy-en-Valois", +"Crézancy-en-Sancerre", +"Crézançay-sur-Cher", +"Crêches-sur-Saône", "Cubières-sur-Cinoble", -"cubito-carpien", -"cubito-carpienne", -"cubito-carpiennes", -"cubito-carpiens", "Cubjac-Auvézère-Val-d'Ans", -"cubo-prismatique", -"cubo-prismatiques", "Cubry-lès-Faverney", "Cubry-lès-Soing", "Cubzac-les-Ponts", -"cucu-la-praline", -"cucul-la-praline", -"cueille-essaim", -"cueille-fruits", -"cueilleur-égreneur", -"cueilleurs-égreneurs", -"cueilleuse-égreneuse", -"cueilleuse-épanouilleuse", -"cueilleuses-égreneuses", -"cueilleuses-épanouilleuses", "Cuges-les-Bains", "Cuges-les-Pins", "Cugliate-Fabiasco", "Cugny-lès-Crouttes", -"cui-cui", "Cuigy-en-Bray", -"çui-là", -"cuir-laine", "Cuiry-Housse", -"cuiry-houssien", "Cuiry-Houssien", -"cuiry-houssienne", "Cuiry-Houssienne", -"cuiry-houssiennes", "Cuiry-Houssiennes", -"cuiry-houssiens", "Cuiry-Houssiens", "Cuiry-lès-Chaudardes", "Cuiry-lès-Iviers", "Cuise-la-Motte", -"cuisse-de-nymphe", -"cuisse-madame", -"cuisse-madames", "Cuissy-et-Geny", "Cuisy-en-Almont", -"cuit-poires", -"cuit-pommes", -"cuit-vapeur", -"cuit-vapeurs", "Cujavie-Poméranie", -"cul-bas", -"cul-bénit", -"cul-blanc", -"cul-brun", -"cul-cul", -"culcul-la-praline", -"cul-culs", -"cul-de-basse-fosse", -"cul-de-bouteille", -"cul-de-chien", -"cul-de-four", -"cul-de-jatte", -"cul-de-lampe", -"cul-de-plomb", -"cul-de-porc", -"cul-de-poule", -"cul-de-sac", -"cul-des-sartois", "Cul-des-Sartois", "Cul-des-Sartoise", "Cul-des-Sarts", -"cul-doré", "Culey-le-Patry", -"culit-api", "Culles-les-Roches", -"cul-levé", -"cul-rouge", -"cul-rousselet", -"culs-bénits", -"culs-blancs", -"culs-de-basse-fosse", -"culs-de-bouteille", -"culs-de-chien", -"culs-de-four", -"culs-de-jatte", -"culs-de-lampe", -"culs-de-plomb", -"culs-de-poule", -"culs-de-sac", -"culs-levés", -"culs-rouges", -"culs-terreux", -"cul-terreux", -"cultivateurs-tasseurs", -"cultivateur-tasseur", -"culturo-scientifique", -"culturo-scientifiques", "Cumières-le-Mort-Homme", -"cumulo-nimbus", "Cuncy-lès-Varzy", -"cunéo-scaphoïdien", -"cupro-allophane", -"cupro-allophanes", -"cupro-aluminium", -"cupro-aluminiums", -"cupro-ammoniacal", -"cupro-elbaïte", -"cupro-elbaïtes", -"cupro-fraipontite", -"cupro-fraipontites", -"cupro-nickel", -"cupro-nickels", "Cuq-Toulza", -"Curçay-sur-Dive", "Curciat-Dongalon", "Curcy-sur-Orne", -"cure-dent", -"cure-dents", -"cure-feu", -"cure-feux", "Cureghem-lez-Bruxelles", -"cure-langue", -"cure-langues", -"cure-môle", -"cure-ongle", -"cure-ongles", -"cure-oreille", -"cure-oreilles", -"cure-pied", -"cure-pieds", -"cure-pipe", -"cure-pipes", "Curis-au-Mont-d'Or", "Cursolo-Orasso", +"Curti-Marignacais", +"Curti-Marignacaise", +"Curti-Marignacaises", "Curtil-Saint-Seine", +"Curtil-Vergy", "Curtil-sous-Buffières", "Curtil-sous-Burnand", -"Curtil-Vergy", -"curti-marignacais", -"Curti-Marignacais", -"curti-marignacaise", -"Curti-Marignacaise", -"curti-marignacaises", -"Curti-Marignacaises", "Curzay-sur-Vonne", +"Curçay-sur-Dive", "Cuse-et-Adrisans", "Cussac-Fort-Médoc", "Cussac-sur-Loire", @@ -6631,68 +3853,62 @@ FR_BASE_EXCEPTIONS = [ "Cussy-la-Colonne", "Cussy-le-Châtel", "Cussy-les-Forges", -"custodi-nos", "Cuttoli-Corticchiato", "Cuverville-sur-Yères", "Cuxac-Cabardès", "Cuxac-d'Aude", -"Cuyk-Sainte-Agathe", "Cuy-Saint-Fiacre", -"cycle-car", -"cycle-cars", -"cyclo-bus", -"cyclo-cross", -"cyclo-draisine", -"cyclo-draisines", -"cyclo-nomade", -"cyclo-nomades", -"cyclo-octyl-diméthylurée", -"cyclo-pousse", -"cyclo-pousses", -"cyhalofop-butyl", -"cylindro-conique", +"Cuyk-Sainte-Agathe", "Cys-la-Commune", -"cyth's", -"cyto-architectonie", -"cyto-architectonies", -"cyto-architectonique", -"cyto-architectoniques", +"Cœuvres-et-Valsery", +"Céaux-d'Allègre", +"Céleste-Empire", +"Cély-en-Bière", +"Cénac-et-Saint-Julien", +"Cérans-Foulletourte", +"Céroux-Mousty", +"Céré-la-Ronde", +"Césarville-Dossainville", +"Côtes-d'Armor", +"Côtes-du-Nord", +"Cœuvres-et-Valsery", +"D'Huison-Longueville", +"D-Day", +"D-glucuronate", +"D-glucuronates", +"D-glycéraldéhyde", +"D-sucre", +"D-sucres", +"DIN-31635", +"DMTA-P", +"DOM-ROM", +"DOM-TOM", +"DVD-RAM", +"DVD-ROM", +"DVD-RW", "Dagny-Lambercy", "Dahme-Forêt-de-Spree", "Dain-en-Saulnois", "Dainville-Bertheléville", -"dalai-lama", -"dalaï-lama", -"dalai-lamas", -"dalaï-lamas", "Dalberg-Wendelstorf", "Dallgow-Döberitz", "Damas-aux-Bois", "Damas-et-Bettegney", "Dambach-la-Ville", "Dambenoît-lès-Colombe", -"dame-aubert", -"dame-d'onze-heures", -"dame-jeanne", "Dame-Marie", "Dame-Marie-les-Bois", -"dame-pipi", -"dame-ronde", -"dames-d'onze-heures", -"dames-jeannes", -"dames-pipi", -"dames-rondes", "Dammarie-en-Puisaye", "Dammarie-les-Lys", "Dammarie-sur-Loing", "Dammarie-sur-Saulx", +"Dammartin-Marpain", "Dammartin-en-Goële", "Dammartin-en-Serve", "Dammartin-les-Templiers", -"Dammartin-Marpain", "Dammartin-sur-Meuse", "Dammartin-sur-Tigeaux", -"d-amphétamine", +"Dampierre-Saint-Nicolas", "Dampierre-au-Temple", "Dampierre-en-Bray", "Dampierre-en-Bresse", @@ -6705,7 +3921,6 @@ FR_BASE_EXCEPTIONS = [ "Dampierre-le-Château", "Dampierre-les-Bois", "Dampierre-lès-Conflans", -"Dampierre-Saint-Nicolas", "Dampierre-sous-Bouhy", "Dampierre-sous-Brou", "Dampierre-sur-Aube", @@ -6713,162 +3928,63 @@ FR_BASE_EXCEPTIONS = [ "Dampierre-sur-Avre", "Dampierre-sur-Blévy", "Dampierre-sur-Boutonne", -"Dampierre-sur-le-Doubs", "Dampierre-sur-Linotte", "Dampierre-sur-Loire", "Dampierre-sur-Moivre", "Dampierre-sur-Salon", -"Dampvalley-lès-Colombe", +"Dampierre-sur-le-Doubs", "Dampvalley-Saint-Pancras", +"Dampvalley-lès-Colombe", "Dancourt-Popincourt", "Dangé-Saint-Romain", "Danne-et-Quatre-Vents", "Dannemarie-sur-Crète", "Dannstadt-Schauernheim", -"danse-poteau", "Danube-Ries", "Danvou-la-Ferrière", -"Dão-Lafões", -"dare-dare", -"dar-et-dar", "Darmstadt-Dieburg", "Darney-aux-Chênes", -"datte-de-mer", +"Daubeuf-Serville", "Daubeuf-la-Campagne", "Daubeuf-près-Vatteville", -"Daubeuf-Serville", "Daumazan-sur-Arize", "Dauzat-sur-Vodable", -"D-Day", -"dead-line", -"dead-lines", -"débat-spectacle", -"Débats-Rivière-d'Orpra", -"débauche-embauche", -"déca-ampère", -"déca-ampères", -"de-ci", -"Décines-Charpieu", -"découd-vite", -"découpe-neige", -"découpes-neige", -"décrochez-moi-ça", -"Dégrad-Edmond", -"Dégrad-Samson", -"déjà-vu", -"de-là", "Delap-Uliga-Darrit", "Delley-Portalban", "Delouze-Rosières", "Demange-aux-Eaux", -"déméton-méthyl", +"Demi-Quartier", "Demitz-Thumitz", -"démocrate-chrétien", -"démocrate-chrétienne", -"démocrates-chrétiennes", -"démocrates-chrétiens", -"démonte-pneu", -"démonte-pneus", -"dena'ina", -"dena'inas", -"Deneuille-lès-Chantelle", "Deneuille-les-Mines", -"Dénezé-sous-Doué", -"Dénezé-sous-le-Lude", +"Deneuille-lès-Chantelle", "Dennweiler-Frohnbach", -"dent-de-cheval", -"dent-de-chien", -"dent-de-lion", -"dent-de-loup", -"dent-de-rat", -"dento-facial", -"dents-de-cheval", -"dents-de-chien", -"dents-de-lion", -"dépose-minute", -"dépôts-ventes", -"dépôt-vente", -"député-maire", -"députés-maires", -"dermato-allergologue", -"dermato-allergologues", -"dernière-née", -"dernier-né", -"dernier-nés", -"derniers-nés", -"des-agreable", -"des-agreables", -"déséthyl-terbuméton", -"dès-méshui", "Dessau-Rosslau", -"dessinateur-typographe", -"dessous-de-bouteille", -"dessous-de-bras", -"dessous-de-plat", -"dessous-de-table", -"dessous-de-tables", -"dessus-de-lit", -"dessus-de-plat", -"dessus-de-porte", -"dessus-de-tête", -"Détain-et-Bruant", "Deuil-la-Barre", "Deux-Acren", -"deux-cents", -"deux-cent-vingt-et-un", "Deux-Chaises", -"deux-chaisois", "Deux-Chaisois", -"deux-chaisoise", "Deux-Chaisoise", -"deux-chaisoises", "Deux-Chaisoises", -"deux-chevaux", -"deux-dents", "Deux-Evailles", -"Deux-Évailles", "Deux-Jumeaux", -"deux-mâts", -"deux-mille", "Deux-Montagnais", +"Deux-Ponts", +"Deux-Rivières", +"Deux-Sèvres", +"Deux-Verges", +"Deux-Évailles", "Deuxnouds-aux-Bois", "Deuxnouds-devant-Beauzée", -"deux-peccable", -"deux-peccables", -"deux-pièces", -"deux-points", -"deux-ponts", -"Deux-Ponts", -"deux-quatre", -"Deux-Rivières", -"deux-roues", -"Deux-Sèvres", -"deux-temps", -"Deux-Verges", -"Déville-lès-Rouen", -"devrai-gondragnier", "Devrai-Gondragnier", -"devrai-gondragnière", -"Devrai-Gondragnière", -"devrai-gondragnières", -"Devrai-Gondragnières", -"devrai-gondragniers", "Devrai-Gondragniers", -"dextro-volubile", +"Devrai-Gondragnière", +"Devrai-Gondragnières", "Dezize-lès-Maranges", -"D-glucuronate", -"D-glucuronates", -"D-glycéraldéhyde", -"di-1-p-menthène", -"diam's", +"Dhuys et Morin-en-Brie", "Diane-Capelle", -"diastéréo-isomère", -"diastéréo-isomères", -"dichloro-diphényl-dichloroéthane", -"dichlorprop-p", -"diclofop-méthyl", "Dieffenbach-au-Val", "Dieffenbach-lès-Woerth", +"Dieffenbach-lès-Wœrth", "Dieffenbach-lès-Wœrth", "Diekhusen-Fahrstedt", "Diennes-Aubigny", @@ -6877,157 +3993,41 @@ FR_BASE_EXCEPTIONS = [ "Diera-Zehren", "Dierrey-Saint-Julien", "Dierrey-Saint-Pierre", -"diesel-électrique", -"diésel-électrique", -"diesels-électriques", -"diésels-électriques", -"diéthyl-diphényl-dichloroéthane", "Dietzenrode-Vatterode", "Dieue-sur-Meuse", "Diffembach-lès-Hellimer", "Digne-les-Bains", -"digue-digue", -"dihydro-oxycodéinone", -"dik-dik", -"dik-diks", -"dikégulac-sodium", "Dilsen-Stokkem", -"diméthénamide-P", -"diméthyl-dixanthogène", -"DIN-31635", -"dîner-spectacle", -"dîners-spectacles", "Dingolfing-Landau", -"Dingy-en-Vuache", "Dingy-Saint-Clair", -"dining-room", -"dining-rooms", +"Dingy-en-Vuache", "Dinsheim-sur-Bruche", "Dio-et-Valquières", -"diola-kasa", "Dion-Valmont", -"diony-sapinois", "Diony-Sapinois", -"diony-sapinoise", "Diony-Sapinoise", -"diony-sapinoises", "Diony-Sapinoises", -"diptéro-sodomie", -"diptéro-sodomies", -"disc-jockey", -"disc-jockeys", "Dissay-sous-Courcillon", "Dissen-Striesow", "Dissé-sous-Ballon", "Dissé-sous-le-Lude", -"distance-temps", "Dittelsheim-Heßloch", "Divatte-sur-Loire", -"divergi-nervé", "Dives-sur-Mer", "Divitz-Spoldershagen", "Divonne-les-Bains", -"dix-cors", -"dix-en-dix", -"dix-heura", -"dix-heurai", -"dix-heuraient", -"dix-heurais", -"dix-heurait", -"dix-heurâmes", -"dix-heurant", -"dix-heuras", -"dix-heurasse", -"dix-heurassent", -"dix-heurasses", -"dix-heurassiez", -"dix-heurassions", -"dix-heurât", -"dix-heurâtes", -"dix-heure", -"dix-heuré", -"dix-heurent", -"dix-heurer", -"dix-heurera", -"dix-heurerai", -"dix-heureraient", -"dix-heurerais", -"dix-heurerait", -"dix-heureras", -"dix-heurèrent", -"dix-heurerez", -"dix-heureriez", -"dix-heurerions", -"dix-heurerons", -"dix-heureront", -"dix-heures", -"dix-heurez", -"dix-heuriez", -"dix-heurions", -"dix-heurons", -"dix-huit", -"dix-huitième", -"dix-huitièmement", -"dix-huitièmes", -"dix-huitiémisme", -"dix-huitiémismes", -"dix-huitiémiste", -"dix-huitièmiste", -"dix-huitiémistes", -"dix-huitièmistes", -"dix-mille", -"dix-millième", -"dix-millièmes", -"dix-millionième", -"dix-millionièmes", -"dix-neuf", -"dix-neuvième", -"dix-neuvièmement", -"dix-neuvièmes", -"dix-neuviémisme", -"dix-neuviémismes", -"dix-neuviémiste", -"dix-neuvièmiste", -"dix-neuviémistes", -"dix-neuvièmistes", -"dix-roues", -"dix-sept", -"dix-septième", -"dix-septièmement", -"dix-septièmes", -"dix-septiémisme", -"dix-septiémismes", -"dix-septiémiste", -"dix-septièmiste", -"dix-septiémistes", -"dix-septièmistes", "Dizy-le-Gros", -"djoumada-l-oula", -"djoumada-t-tania", -"DMTA-P", -"doati-casteidois", "Doati-Casteidois", -"doati-casteidoise", "Doati-Casteidoise", -"doati-casteidoises", "Doati-Casteidoises", "Dobbin-Linstow", "Doberlug-Kirchhain", "Doberschau-Gaußig", -"docu-fiction", -"docu-fictions", -"documentaire-choc", -"documentaires-chocs", -"dodémorphe-acétate", -"Dœuil-sur-le-Mignon", -"dog-cart", -"dog-carts", "Dohm-Lammersdorf", -"doigt-de-gant", -"doigts-de-gant", "Dol-de-Bretagne", "Dolus-d'Oléron", "Dolus-le-Sec", +"Dom-le-Mesnil", "Domart-en-Ponthieu", "Domart-sur-la-Luce", "Dombasle-devant-Darney", @@ -7040,21 +4040,15 @@ FR_BASE_EXCEPTIONS = [ "Domburg-Buiten", "Domecy-sur-Cure", "Domecy-sur-le-Vault", -"Domèvre-en-Haye", -"Domèvre-sous-Montfort", -"Domèvre-sur-Avière", -"Domèvre-sur-Durbion", -"Domèvre-sur-Vezouze", "Domezain-Berraute", "Domfront-en-Champagne", "Domfront-en-Poiraie", "Domléger-Longvillers", -"Dom-le-Mesnil", -"dommage-intérêt", -"dommages-intérêts", "Dommarie-Eulmont", -"Dommartin-aux-Bois", "Dommartin-Dampierre", +"Dommartin-Lettrée", +"Dommartin-Varimont", +"Dommartin-aux-Bois", "Dommartin-la-Chapelle", "Dommartin-la-Chaussée", "Dommartin-la-Montagne", @@ -7065,22 +4059,20 @@ FR_BASE_EXCEPTIONS = [ "Dommartin-lès-Remiremont", "Dommartin-lès-Toul", "Dommartin-lès-Vallois", -"Dommartin-Lettrée", "Dommartin-sous-Amance", "Dommartin-sous-Hans", "Dommartin-sur-Vraine", -"Dommartin-Varimont", "Dommary-Baroncourt", "Domnom-lès-Dieuze", "Domnon-lès-Dieuze", -"Dompierre-aux-Bois", "Dompierre-Becquincourt", +"Dompierre-aux-Bois", "Dompierre-du-Chemin", "Dompierre-en-Morvan", "Dompierre-les-Eglises", -"Dompierre-les-Églises", "Dompierre-les-Ormes", "Dompierre-les-Tilleuls", +"Dompierre-les-Églises", "Dompierre-sous-Sanvignes", "Dompierre-sur-Authie", "Dompierre-sur-Besbre", @@ -7094,199 +4086,79 @@ FR_BASE_EXCEPTIONS = [ "Dompierre-sur-Veyle", "Dompierre-sur-Yon", "Domptail-en-l'Air", -"dompte-venin", -"dompte-venins", +"Domremy-Landéville", "Domremy-aux-Bois", "Domremy-la-Canne", -"Domremy-Landéville", "Domrémy-la-Pucelle", -"DOM-ROM", -"DOM-TOM", -"dom-tomien", -"dom-tomienne", -"dom-tomiennes", -"dom-tomiens", -"donation-partage", -"donations-partages", +"Domèvre-en-Haye", +"Domèvre-sous-Montfort", +"Domèvre-sur-Avière", +"Domèvre-sur-Durbion", +"Domèvre-sur-Vezouze", "Donchery-sur-Meuse", "Doncourt-aux-Templiers", "Doncourt-lès-Conflans", "Doncourt-lès-Longuyon", "Doncourt-sur-Meuse", "Dongen-Vaart", -"don-juanisme", -"don-juanismes", -"donnant-donnant", -"donne-jour", "Donnemain-Saint-Mamès", "Donnemarie-Dontilly", -"don-quichottisme", -"don-quichottismes", "Donville-les-Bains", "Donzy-le-National", "Donzy-le-Pertuis", -"doom-death", "Dore-l'Eglise", "Dore-l'Église", -"Dörfles-Esbach", -"Dornburg-Camburg", "Dorn-Dürkheim", -"dorso-vélaire", -"dorso-vélaires", -"dos-d'âne", +"Dornburg-Camburg", "Dossenheim-Kochersberg", "Dossenheim-sur-Zinsel", -"doubet-talibautier", "Doubet-Talibautier", -"doubet-talibautière", -"Doubet-Talibautière", -"doubet-talibautières", -"Doubet-Talibautières", -"doubet-talibautiers", "Doubet-Talibautiers", -"doubles-aubiers", -"doubles-bécassines", -"doubles-bouches", -"doubles-bulbes", -"doubles-canons", -"doubles-chaînes", -"doubles-clics", -"doubles-croches", -"doubles-feuilles", -"doubles-fonds", -"doubles-mains", -"doubles-sens", -"douce-amère", -"douces-amères", -"Douchy-lès-Ayette", -"Douchy-les-Mines", +"Doubet-Talibautière", +"Doubet-Talibautières", "Douchy-Montcorbon", +"Douchy-les-Mines", +"Douchy-lès-Ayette", "Doucy-en-Bauges", "Doudeauville-en-Vexin", -"Doué-en-Anjou", -"Doué-la-Fontaine", "Doulaincourt-Saucourt", "Doulevant-le-Château", "Doulevant-le-Petit", -"dou-l-hidjja", -"dou-l-qa'da", "Doumely-Bégny", "Dourd'Hal", "Douville-en-Auge", "Douville-sur-Andelle", "Douvres-la-Délivrande", -"doux-agnel", -"doux-à-l'agneau", -"doux-amer", -"doux-amers", -"doux-ballon", -"doux-vert", "Douy-la-Ramée", -"down-loada", -"down-loadai", -"down-loadaient", -"down-loadais", -"down-loadait", -"down-loadâmes", -"down-loadant", -"down-loadas", -"down-loadasse", -"down-loadassent", -"down-loadasses", -"down-loadassiez", -"down-loadassions", -"down-loadât", -"down-loadâtes", -"down-loade", -"down-loadé", -"down-loadée", -"down-loadées", -"down-loadent", -"down-loader", -"down-loadera", -"down-loaderai", -"down-loaderaient", -"down-loaderais", -"down-loaderait", -"down-loaderas", -"down-loadèrent", -"down-loaderez", -"down-loaderiez", -"down-loaderions", -"down-loaderons", -"down-loaderont", -"down-loades", -"down-loadés", -"down-loadez", -"down-loadiez", -"down-loadions", -"down-loadons", +"Doué-en-Anjou", +"Doué-la-Fontaine", "Drachenbronn-Birlenbach", +"Dracy-Saint-Loup", "Dracy-le-Fort", "Dracy-lès-Couches", -"Dracy-Saint-Loup", "Dracy-sur-Ouanne", "Dragey-Ronthon", -"dragonnet-lyre", -"drainage-taupe", -"draineuses-trancheuses", -"draineuse-trancheuse", -"drap-housse", -"drap-housses", "Dreis-Brück", -"drelin-drelin", -"Drémil-Lafage", "Dreuil-Hamel", "Dreuil-lès-Amiens", "Dreuil-lès-Molliens", "Driebergen-Rijsenburg", -"drift-ice", -"drift-ices", -"dring-dring", -"drive-in", -"drive-ins", -"drive-way", -"drive-ways", -"droit-fil", -"droit-fils", -"drop-goal", -"drop-goals", "Droue-sur-Drouette", "Droupt-Saint-Basle", "Droupt-Sainte-Marie", "Drouvin-le-Marais", -"drug-store", -"drug-stores", "Drumettaz-Clarafond", -"Druyes-les-Belles-Fontaines", "Druy-Parigny", -"dry-tooleur", -"dry-tooleurs", -"dry-tooling", -"D-sucre", -"D-sucres", -"dual-core", -"dual-cores", -"duc-d'albe", -"duc-d'Albe", +"Druyes-les-Belles-Fontaines", +"Drémil-Lafage", "Duc-de-Thol", -"duché-pairie", -"duchés-pairies", -"ducs-d'albe", -"ducs-d'Albe", +"Ducey-Les Chéris", "Ducy-Sainte-Marguerite", -"duffel-coat", -"duffel-coats", -"duffle-coat", -"duffle-coats", "Dugny-sur-Meuse", "Duhamellois-de-l'Ouest", "Duhort-Bachen", "Duilhac-sous-Peyrepertuse", "Duino-Aurisina", -"dum-dum", -"Dunières-sur-Eyrieux", -"Dunière-sur-Eyrieux", "Dun-le-Palestel", "Dun-le-Palleteau", "Dun-le-Poëlier", @@ -7294,350 +4166,115 @@ FR_BASE_EXCEPTIONS = [ "Dun-sur-Auron", "Dun-sur-Grandry", "Dun-sur-Meuse", -"duo-tang", -"duo-tangs", -"duplicato-dentelé", +"Dunière-sur-Eyrieux", +"Dunières-sur-Eyrieux", "Dupont-Lajoie", "Durban-Corbières", "Durban-sur-Arize", -"dur-bec", "Durdat-Larequille", -"dure-mère", -"dure-peau", -"dures-mères", -"dures-peaux", -"Durfort-et-Saint-Martin-de-Sossenac", "Durfort-Lacapelette", +"Durfort-et-Saint-Martin-de-Sossenac", +"Dœuil-sur-le-Mignon", +"Dão-Lafões", +"Débats-Rivière-d'Orpra", +"Décines-Charpieu", +"Dégrad-Edmond", +"Dégrad-Samson", +"Dénezé-sous-Doué", +"Dénezé-sous-le-Lude", +"Détain-et-Bruant", +"Déville-lès-Rouen", +"Dörfles-Esbach", "Dürrröhrsdorf-Dittersbach", -"durs-becs", -"duty-free", -"DVD-RAM", -"DVD-ROM", -"DVD-RW", -"dynamo-électrique", -"dynamo-électriques", +"Dœuil-sur-le-Mignon", "E7,Z9-12:Ac", "E7-Z9-dodécadiénylacétate", "E8,E10-dodécadiène-1-ol", -"e-administration", -"e-administrations", -"eau-bénitier", -"eau-bénitiers", +"EE-8,10-DDDOL", "Eaucourt-sur-Somme", -"eau-de-vie", -"eau-forte", -"eaux-bonnais", "Eaux-Bonnais", -"eaux-bonnaise", "Eaux-Bonnaise", -"eaux-bonnaises", "Eaux-Bonnaises", "Eaux-Bonnes", -"eaux-de-vie", -"eaux-fortes", "Eaux-Puiseaux", -"eaux-vannes", -"Ében-Émael", "Eberbach-Seltz", "Eberbach-Wœrth", "Ebersbach-Musbach", "Ebnat-Kappel", -"e-book", -"e-business", "Ecalles-Alix", -"Écalles-Alix", "Ecardenville-la-Campagne", -"Écardenville-la-Campagne", "Ecardenville-sur-Eure", -"Écardenville-sur-Eure", -"e-carte", -"e-cartes", -"écarts-types", -"écart-type", -"Écaussinnes-d'Enghien", -"Écaussinnes-Lalaing", "Eccica-Suarella", "Echarri-Aranaz", "Echelle-Saint-Aurin", -"Échelle-Saint-Aurin", "Echenans-sous-Mont-Vaudois", -"Échenans-sous-Mont-Vaudois", "Echenoz-la-Méline", -"Échenoz-la-Méline", "Echenoz-le-Sec", -"Échenoz-le-Sec", -"écho-location", -"écho-locations", -"échos-radars", "Echt-Susteren", -"e-cig", -"e-cigarette", -"e-cigarettes", -"e-cigs", -"e-cinéma", -"e-cinémas", "Eclans-Nenon", -"Éclans-Nenon", "Eclaron-Braucourt-Sainte-Livière", -"Éclaron-Braucourt-Sainte-Livière", -"e-client", -"e-clope", -"e-clopes", "Eclose-Badinières", "Eclusier-Vaux", -"Éclusier-Vaux", "Ecole-Valentin", -"École-Valentin", -"e-commerçant", -"e-commerçants", -"e-commerce", -"écorche-œil", -"Ecotay-l'Olme", -"Écotay-l'Olme", "Ecot-la-Combe", -"Écot-la-Combe", -"Écouché-les-Vallées", -"e-couponing", +"Ecotay-l'Olme", "Ecourt-Saint-Quentin", -"Écourt-Saint-Quentin", "Ecoust-Saint-Mein", -"Écoust-Saint-Mein", -"écoute-s'il-pleut", -"Écoute-s'il-pleut", -"écrase-merde", -"écrase-merdes", "Ecretteville-lès-Baons", -"Écretteville-lès-Baons", "Ecretteville-sur-Mer", -"Écretteville-sur-Mer", -"e-criminalité", -"e-criminalités", -"Écry-le-Franc", "Ectot-l'Auber", "Ectot-lès-Baons", "Ecurey-en-Verdunois", -"Écurey-en-Verdunois", -"écurie-ménagerie", -"écuries-ménageries", "Ecury-le-Repos", -"Écury-le-Repos", "Ecury-sur-Coole", -"Écury-sur-Coole", "Edam-Volendam", -"e-délinquance", -"e-délinquances", "Ediger-Eller", "Edingen-Neckarhausen", -"edit-a-thon", -"edit-a-thons", -"Édouard-Josse", -"EE-8,10-DDDOL", "Eelde-Paterswolde", "Effelder-Rauenstein", -"effet-bulle", -"effets-bulles", "Efringen-Kirchen", -"égal-à-tous", -"Egée-Méridionale", -"Égée-Méridionale", -"Egée-Septentrionale", -"Égée-Septentrionale", "Eggenstein-Leopoldshafen", -"Eglise-aux-Bois", -"Église-aux-Bois", -"église-halle", -"Egliseneuve-d'Entraigues", -"Égliseneuve-d'Entraigues", -"Egliseneuve-des-Liards", -"Égliseneuve-des-Liards", -"Eglise-Neuve-de-Vergt", -"Église-Neuve-de-Vergt", "Eglise-Neuve-d'Issac", -"Église-Neuve-d'Issac", +"Eglise-Neuve-de-Vergt", +"Eglise-aux-Bois", +"Egliseneuve-d'Entraigues", +"Egliseneuve-des-Liards", "Egliseneuve-près-Billom", -"Égliseneuve-près-Billom", "Egmond-Binnen", -"ego-document", -"ego-documents", "Egriselles-le-Bocage", -"Égriselles-le-Bocage", "Eguille-sur-Seudre", -"Éguille-sur-Seudre", "Eguilly-sous-Bois", -"Éguilly-sous-Bois", "Eguzon-Chantôme", -"Éguzon-Chantôme", -"égypto-lybien", -"égypto-tchado-soudanais", -"Éhein-bas", +"Egée-Méridionale", +"Egée-Septentrionale", "Ehlange-sur-Mess", "Ehra-Lessien", "Eifel-Bitburg-Prüm", "Eijsden-Margraten", "Einville-au-Jard", -"éka-actinide", -"éka-actinides", -"éka-aluminium", -"éka-astate", -"éka-bismuth", -"éka-bore", -"éka-borium", -"éka-francium", -"éka-mercure", -"éka-plomb", -"éka-polonium", -"éka-prométhium", -"éka-silicium", -"e-la", -"e-la-fa", -"e-la-mi", -"el-âsker", "Elbe-Elster", "Elbe-Parey", "Elbeuf-en-Bray", "Elbeuf-sur-Andelle", "Elburgo-Burgelu", "Elchesheim-Illingen", -"électron-volt", -"électron-volts", -"élément-clé", -"éléments-clés", "Eleu-dit-Leauwette", -"Éleu-dit-Leauwette", "Elincourt-Sainte-Marguerite", -"Élincourt-Sainte-Marguerite", "Elisabeth-Sophien-Koog", "Elise-Daucourt", -"Élise-Daucourt", -"elle-même", "Ellenz-Poltersdorf", -"elles-mêmes", "Ellignies-Sainte-Anne", -"ello-rhénan", -"ello-rhénane", -"ello-rhénanes", -"ello-rhénans", "Elsdorf-Westermühlen", "Elvillar-Bilar", -"e-mail", -"e-maila", -"e-mailai", -"e-mailaient", -"e-mailais", -"e-mailait", -"e-mailâmes", -"e-mailant", -"e-mailas", -"e-mailasse", -"e-mailassent", -"e-mailasses", -"e-mailassiez", -"e-mailassions", -"e-mailât", -"e-mailâtes", -"e-maile", -"e-mailé", -"e-mailée", -"e-mailées", -"e-mailent", -"e-mailer", -"e-mailera", -"e-mailerai", -"e-maileraient", -"e-mailerais", -"e-mailerait", -"e-maileras", -"e-mailèrent", -"e-mailerez", -"e-maileriez", -"e-mailerions", -"e-mailerons", -"e-maileront", -"e-mailes", -"e-mailés", -"e-maileur", -"e-maileurs", -"e-maileuse", -"e-maileuses", -"e-mailez", -"e-mailiez", -"e-mailing", -"e-mailings", -"e-mailions", -"e-mailons", -"e-marketeur", -"e-marketeurs", -"e-marketeuse", -"e-marketeuses", -"e-marketing", -"e-marketings", -"emballage-bulle", -"emballage-coque", -"emballages-bulles", -"emballages-coques", "Embres-et-Castelmaure", -"e-merchandiser", -"émetteur-récepteur", -"émetteur-récepteurs", -"émilienne-romagnole", -"Émilienne-Romagnole", -"émiliennes-romagnoles", -"Émiliennes-Romagnoles", -"émilien-romagnol", -"Émilien-Romagnol", -"émiliens-romagnols", -"Émiliens-Romagnols", -"Émilie-Romagne", -"émirato-algérien", -"émirato-allemand", -"émirato-allemands", -"émirato-britannique", -"émirato-britanniques", -"émirato-helvétique", -"émirato-helvétiques", -"émirato-indien", -"émirato-iranien", -"émirato-japonais", -"émission-débat", "Emmelsbüll-Horsbüll", "Emmer-Compascuum", "Emmer-Erfscheidenveen", "Emmingen-Liptingen", -"emo-sexualité", -"emo-sexualités", -"emporte-pièce", -"emporte-pièces", -"énargite-beta", -"énargite-betas", -"en-avant", -"en-avants", -"en-but", -"en-buts", -"en-cas", "Encausse-les-Thermes", "Enclave-de-la-Martinière", -"en-cours", -"en-deçà", -"en-dessous", -"en-dessus", "Enencourt-Léage", -"Énencourt-Léage", "Enencourt-le-Sec", -"Énencourt-le-Sec", -"enfant-bulle", -"enfant-roi", -"enfants-bulles", -"enfant-soldat", -"enfants-robots", -"enfants-rois", -"enfants-soldats", -"enfile-aiguille", -"enfile-aiguilles", -"enfle-boeuf", -"enfle-bœuf", -"enfle-boeufs", -"enfle-bœufs", -"en-garant", "Enge-Sande", "Enghien-les-Bains", "Englesqueville-en-Auge", @@ -7645,2204 +4282,65 @@ FR_BASE_EXCEPTIONS = [ "Enkenbach-Alsenborn", "Ennepe-Ruhr", "Ennetières-en-Weppes", -"enquêtes-minute", "Enquin-les-Mines", "Enquin-lez-Guinegatte", "Enquin-sur-Baillons", -"enseignant-chercheur", -"enseignante-chercheuse", -"enseignantes-chercheuses", -"enseignants-chercheurs", "Ensuès-la-Redonne", -"entéro-colite", -"entéro-colites", -"entéro-cystocèle", -"entéro-épiplocèle", -"entéro-épiplocèles", -"entéro-hémorrhagie", -"entéro-hydrocèle", -"entéro-hydromphale", -"entéro-mérocèle", -"entéro-mésentérite", -"entéro-pneumatose", -"entéro-rénal", -"entéro-rénale", -"entéro-rénales", -"entéro-rénaux", -"entéro-sarcocèle", -"entéro-sarcocèles", -"entéro-sténose", -"entéro-sténoses", -"en-tête", -"en-têtes", -"en-tout-cas", -"entr'abat", -"entr'abattaient", -"entr'abattait", -"entr'abattant", -"entr'abatte", -"entr'abattent", -"entr'abattez", -"entr'abattiez", -"entr'abattîmes", -"entr'abattions", -"entr'abattirent", -"entr'abattissent", -"entr'abattissions", -"entr'abattit", -"entr'abattît", -"entr'abattîtes", -"entr'abattons", -"entr'abattra", -"entr'abattraient", -"entr'abattrait", -"entr'abattre", -"entr'abattre", -"entr'abattrez", -"entr'abattriez", -"entr'abattrions", -"entr'abattrons", -"entr'abattront", -"entr'abattu", -"entr'abattue", -"entr'abattues", -"entr'abattus", -"entr'aborda", -"entr'abordaient", -"entr'abordait", -"entr'abordâmes", -"entr'abordant", -"entr'abordassent", -"entr'abordassiez", -"entr'abordassions", -"entr'abordât", -"entr'abordâtes", -"entr'aborde", -"entr'abordé", -"entr'abordées", -"entr'abordent", -"entr'aborder", -"entr'aborder", -"entr'abordera", -"entr'aborderaient", -"entr'aborderait", -"entr'abordèrent", -"entr'aborderez", -"entr'aborderiez", -"entr'aborderions", -"entr'aborderons", -"entr'aborderont", -"entr'abordés", -"entr'abordez", -"entr'abordiez", -"entr'abordions", -"entr'abordons", -"entr'accola", -"entr'accolaient", -"entr'accolait", -"entr'accolâmes", -"entr'accolant", -"entr'accolassent", -"entr'accolassiez", -"entr'accolassions", -"entr'accolât", -"entr'accolâtes", -"entr'accole", -"entr'accolé", -"entr'accolées", -"entr'accolent", -"entr'accoler", -"entr'accoler", -"entr'accolera", -"entr'accoleraient", -"entr'accolerait", -"entr'accolèrent", -"entr'accolerez", -"entr'accoleriez", -"entr'accolerions", -"entr'accolerons", -"entr'accoleront", -"entr'accolés", -"entr'accolez", -"entr'accoliez", -"entr'accolions", -"entr'accolons", -"entr'accorda", -"entr'accordaient", -"entr'accordait", -"entr'accordâmes", -"entr'accordant", -"entr'accordassent", -"entr'accordassiez", -"entr'accordassions", -"entr'accordât", -"entr'accordâtes", -"entr'accorde", -"entr'accordé", -"entr'accordées", -"entr'accordent", -"entr'accorder", -"entr'accorder", -"entr'accordera", -"entr'accorderaient", -"entr'accorderait", -"entr'accordèrent", -"entr'accorderez", -"entr'accorderiez", -"entr'accorderions", -"entr'accorderons", -"entr'accorderont", -"entr'accordés", -"entr'accordez", -"entr'accordiez", -"entr'accordions", -"entr'accordons", -"entr'accrocha", -"entr'accrochaient", -"entr'accrochait", -"entr'accrochâmes", -"entr'accrochant", -"entr'accrochassent", -"entr'accrochassiez", -"entr'accrochassions", -"entr'accrochât", -"entr'accrochâtes", -"entr'accroche", -"entr'accroché", -"entr'accrochées", -"entr'accrochent", -"entr'accrocher", -"entr'accrocher", -"entr'accrochera", -"entr'accrocheraient", -"entr'accrocherait", -"entr'accrochèrent", -"entr'accrocherez", -"entr'accrocheriez", -"entr'accrocherions", -"entr'accrocherons", -"entr'accrocheront", -"entr'accrochés", -"entr'accrochez", -"entr'accrochiez", -"entr'accrochions", -"entr'accrochons", -"entr'accusa", -"entr'accusaient", -"entr'accusait", -"entr'accusâmes", -"entr'accusant", -"entr'accusassent", -"entr'accusassiez", -"entr'accusassions", -"entr'accusât", -"entr'accusâtes", -"entr'accuse", -"entr'accusé", -"entr'accusées", -"entr'accusent", -"entr'accuser", -"entr'accuser", -"entr'accusera", -"entr'accuseraient", -"entr'accuserait", -"entr'accusèrent", -"entr'accuserez", -"entr'accuseriez", -"entr'accuserions", -"entr'accuserons", -"entr'accuseront", -"entr'accusés", -"entr'accusez", -"entr'accusiez", -"entr'accusions", -"entr'accusons", -"entr'acte", -"entr'actes", -"entr'adapta", -"entr'adaptaient", -"entr'adaptait", -"entr'adaptâmes", -"entr'adaptant", -"entr'adaptassent", -"entr'adaptassiez", -"entr'adaptassions", -"entr'adaptât", -"entr'adaptâtes", -"entr'adapte", -"entr'adapté", -"entr'adaptées", -"entr'adaptent", -"entr'adapter", -"entr'adapter", -"entr'adaptera", -"entr'adapteraient", -"entr'adapterait", -"entr'adaptèrent", -"entr'adapterez", -"entr'adapteriez", -"entr'adapterions", -"entr'adapterons", -"entr'adapteront", -"entr'adaptés", -"entr'adaptez", -"entr'adaptiez", -"entr'adaptions", -"entr'adaptons", -"entr'admira", -"entr'admirai", -"entr'admiraient", -"entr'admirais", -"entr'admirait", -"entr'admirâmes", -"entr'admirant", -"entr'admiras", -"entr'admirasse", -"entr'admirassent", -"entr'admirasses", -"entr'admirassiez", -"entr'admirassions", -"entr'admirât", -"entr'admirâtes", -"entr'admire", -"entr'admiré", -"entr'admirée", -"entr'admirées", -"entr'admirent", -"entr'admirer", -"entr'admirer", -"entr'admirera", -"entr'admirerai", -"entr'admireraient", -"entr'admirerais", -"entr'admirerait", -"entr'admireras", -"entr'admirèrent", -"entr'admirerez", -"entr'admireriez", -"entr'admirerions", -"entr'admirerons", -"entr'admireront", -"entr'admires", -"entr'admirés", -"entr'admirez", -"entr'admiriez", -"entr'admirions", -"entr'admirons", -"entr'admonesta", -"entr'admonestaient", -"entr'admonestait", -"entr'admonestâmes", -"entr'admonestant", -"entr'admonestassent", -"entr'admonestassiez", -"entr'admonestassions", -"entr'admonestât", -"entr'admonestâtes", -"entr'admoneste", -"entr'admonesté", -"entr'admonestées", -"entr'admonestent", -"entr'admonester", -"entr'admonester", -"entr'admonestera", -"entr'admonesteraient", -"entr'admonesterait", -"entr'admonestèrent", -"entr'admonesterez", -"entr'admonesteriez", -"entr'admonesterions", -"entr'admonesterons", -"entr'admonesteront", -"entr'admonestés", -"entr'admonestez", -"entr'admonestiez", -"entr'admonestions", -"entr'admonestons", -"entr'adressa", -"entr'adressaient", -"entr'adressait", -"entr'adressâmes", -"entr'adressant", -"entr'adressassent", -"entr'adressassiez", -"entr'adressassions", -"entr'adressât", -"entr'adressâtes", -"entr'adresse", -"entr'adressé", -"entr'adressées", -"entr'adressent", -"entr'adresser", -"entr'adresser", -"entr'adressera", -"entr'adresseraient", -"entr'adresserait", -"entr'adressèrent", -"entr'adresserez", -"entr'adresseriez", -"entr'adresserions", -"entr'adresserons", -"entr'adresseront", -"entr'adressés", -"entr'adressez", -"entr'adressiez", -"entr'adressions", -"entr'adressons", -"entr'affronta", -"entr'affrontaient", -"entr'affrontait", -"entr'affrontâmes", -"entr'affrontant", -"entr'affrontassent", -"entr'affrontassiez", -"entr'affrontassions", -"entr'affrontât", -"entr'affrontâtes", -"entr'affronte", -"entr'affronté", -"entr'affrontées", -"entr'affrontent", -"entr'affronter", -"entr'affronter", -"entr'affrontera", -"entr'affronteraient", -"entr'affronterait", -"entr'affrontèrent", -"entr'affronterez", -"entr'affronteriez", -"entr'affronterions", -"entr'affronterons", -"entr'affronteront", -"entr'affrontés", -"entr'affrontez", -"entr'affrontiez", -"entr'affrontions", -"entr'affrontons", -"entr'aida", -"entr'aidaient", -"entr'aidait", -"entr'aidâmes", -"entr'aidant", -"entr'aidassent", -"entr'aidassiez", -"entr'aidassions", -"entr'aidât", -"entr'aidâtes", -"entr'aide", -"entr'aidé", -"entr'aidées", -"entr'aident", -"entr'aider", -"entr'aider", -"entr'aidera", -"entr'aideraient", -"entr'aiderait", -"entr'aidèrent", -"entr'aiderez", -"entr'aideriez", -"entr'aiderions", -"entr'aiderons", -"entr'aideront", -"entr'aides", -"entr'aidés", -"entr'aidez", -"entr'aidiez", -"entr'aidions", -"entr'aidons", "Entraigues-sur-la-Sorgue", -"entr'aiguisa", -"entr'aiguisaient", -"entr'aiguisait", -"entr'aiguisâmes", -"entr'aiguisant", -"entr'aiguisassent", -"entr'aiguisassiez", -"entr'aiguisassions", -"entr'aiguisât", -"entr'aiguisâtes", -"entr'aiguise", -"entr'aiguisé", -"entr'aiguisées", -"entr'aiguisent", -"entr'aiguiser", -"entr'aiguiser", -"entr'aiguisera", -"entr'aiguiseraient", -"entr'aiguiserait", -"entr'aiguisèrent", -"entr'aiguiserez", -"entr'aiguiseriez", -"entr'aiguiserions", -"entr'aiguiserons", -"entr'aiguiseront", -"entr'aiguisés", -"entr'aiguisez", -"entr'aiguisiez", -"entr'aiguisions", -"entr'aiguisons", -"entr'aima", -"entr'aimai", -"entr'aimaient", -"entr'aimais", -"entr'aimait", -"entr'aimâmes", -"entr'aimant", -"entr'aimas", -"entr'aimasse", -"entr'aimassent", -"entr'aimasses", -"entr'aimassiez", -"entr'aimassions", -"entr'aimât", -"entr'aimâtes", -"entr'aime", -"entr'aimé", -"entr'aimée", -"entr'aimées", -"entr'aiment", -"entr'aimer", -"entr'aimer", -"entr'aimera", -"entr'aimerai", -"entr'aimeraient", -"entr'aimerais", -"entr'aimerait", -"entr'aimeras", -"entr'aimèrent", -"entr'aimerez", -"entr'aimeriez", -"entr'aimerions", -"entr'aimerons", -"entr'aimeront", -"entr'aimes", -"entr'aimés", -"entr'aimez", -"entr'aimiez", -"entr'aimions", -"entr'aimons", "Entrains-sur-Nohain", -"entr'anima", -"entr'animaient", -"entr'animait", -"entr'animâmes", -"entr'animant", -"entr'animassent", -"entr'animassiez", -"entr'animassions", -"entr'animât", -"entr'animâtes", -"entr'anime", -"entr'animé", -"entr'animées", -"entr'animent", -"entr'animer", -"entr'animer", -"entr'animera", -"entr'animeraient", -"entr'animerait", -"entr'animèrent", -"entr'animerez", -"entr'animeriez", -"entr'animerions", -"entr'animerons", -"entr'animeront", -"entr'animés", -"entr'animez", -"entr'animiez", -"entr'animions", -"entr'animons", -"entr'apercevaient", -"entr'apercevais", -"entr'apercevait", -"entr'apercevant", -"entr'apercevez", -"entr'aperceviez", -"entr'apercevions", -"entr'apercevoir", -"entr'apercevons", -"entr'apercevra", -"entr'apercevrai", -"entr'apercevraient", -"entr'apercevrais", -"entr'apercevrait", -"entr'apercevras", -"entr'apercevrez", -"entr'apercevriez", -"entr'apercevrions", -"entr'apercevrons", -"entr'apercevront", -"entr'aperçois", -"entr'aperçoit", -"entr'aperçoive", -"entr'aperçoivent", -"entr'aperçoives", -"entr'aperçu", -"entr'aperçue", -"entr'aperçues", -"entr'aperçûmes", -"entr'aperçurent", -"entr'aperçus", -"entr'aperçusse", -"entr'aperçussent", -"entr'aperçusses", -"entr'aperçussiez", -"entr'aperçussions", -"entr'aperçut", -"entr'aperçût", -"entr'aperçûtes", -"entr'apparais", -"entr'apparaissaient", -"entr'apparaissais", -"entr'apparaissait", -"entr'apparaissant", -"entr'apparaisse", -"entr'apparaissent", -"entr'apparaisses", -"entr'apparaissez", -"entr'apparaissiez", -"entr'apparaissions", -"entr'apparaissons", -"entr'apparait", -"entr'apparaît", -"entr'apparaitra", -"entr'apparaîtra", -"entr'apparaitrai", -"entr'apparaîtrai", -"entr'apparaitraient", -"entr'apparaîtraient", -"entr'apparaitrais", -"entr'apparaîtrais", -"entr'apparaitrait", -"entr'apparaîtrait", -"entr'apparaitras", -"entr'apparaîtras", -"entr'apparaitre", -"entr'apparaître", -"entr'apparaitrez", -"entr'apparaîtrez", -"entr'apparaitriez", -"entr'apparaîtriez", -"entr'apparaitrions", -"entr'apparaîtrions", -"entr'apparaitrons", -"entr'apparaîtrons", -"entr'apparaitront", -"entr'apparaîtront", -"entr'apparu", -"entr'apparue", -"entr'apparues", -"entr'apparûmes", -"entr'apparurent", -"entr'apparus", -"entr'apparusse", -"entr'apparussent", -"entr'apparusses", -"entr'apparussiez", -"entr'apparussions", -"entr'apparut", -"entr'apparût", -"entr'apparûtes", -"entr'appela", -"entr'appelaient", -"entr'appelait", -"entr'appelâmes", -"entr'appelant", -"entr'appelassent", -"entr'appelassiez", -"entr'appelassions", -"entr'appelât", -"entr'appelâtes", -"entr'appelé", -"entr'appelées", -"entr'appeler", -"entr'appeler", -"entr'appelèrent", -"entr'appelés", -"entr'appelez", -"entr'appeliez", -"entr'appelions", -"entr'appelle", -"entr'appellent", -"entr'appellera", -"entr'appelleraient", -"entr'appellerait", -"entr'appellerez", -"entr'appelleriez", -"entr'appellerions", -"entr'appellerons", -"entr'appelleront", -"entr'appelles", -"entr'appelons", -"entr'apprenaient", -"entr'apprenait", -"entr'apprenant", -"entr'apprend", -"entr'apprendra", -"entr'apprendraient", -"entr'apprendrait", -"entr'apprendre", -"entr'apprendre", -"entr'apprendriez", -"entr'apprendrions", -"entr'apprendrons", -"entr'apprendront", -"entr'apprenez", -"entr'appreniez", -"entr'apprenions", -"entr'apprenne", -"entr'apprennent", -"entr'apprennes", -"entr'apprenons", -"entr'apprîmes", -"entr'apprirent", -"entr'appris", -"entr'apprise", -"entr'apprises", -"entr'apprissent", -"entr'apprissiez", -"entr'apprissions", -"entr'apprit", -"entr'apprît", -"entr'apprîtes", -"entr'approcha", -"entr'approchaient", -"entr'approchait", -"entr'approchâmes", -"entr'approchant", -"entr'approchassent", -"entr'approchassiez", -"entr'approchassions", -"entr'approchât", -"entr'approchâtes", -"entr'approche", -"entr'approché", -"entr'approchées", -"entr'approchent", -"entr'approcher", -"entr'approcher", -"entr'approchera", -"entr'approcheraient", -"entr'approcherait", -"entr'approchèrent", -"entr'approcherez", -"entr'approcheriez", -"entr'approcherions", -"entr'approcherons", -"entr'approcheront", -"entr'approchés", -"entr'approchez", -"entr'approchiez", -"entr'approchions", -"entr'approchons", -"entr'arquebusa", -"entr'arquebusaient", -"entr'arquebusait", -"entr'arquebusâmes", -"entr'arquebusant", -"entr'arquebusassent", -"entr'arquebusassiez", -"entr'arquebusassions", -"entr'arquebusât", -"entr'arquebusâtes", -"entr'arquebuse", -"entr'arquebusé", -"entr'arquebusées", -"entr'arquebusent", -"entr'arquebuser", -"entr'arquebuser", -"entr'arquebusera", -"entr'arquebuseraient", -"entr'arquebuserait", -"entr'arquebusèrent", -"entr'arquebuserez", -"entr'arquebuseriez", -"entr'arquebuserions", -"entr'arquebuserons", -"entr'arquebuseront", -"entr'arquebusés", -"entr'arquebusez", -"entr'arquebusiez", -"entr'arquebusions", -"entr'arquebusons", -"entr'assassina", -"entr'assassinaient", -"entr'assassinait", -"entr'assassinâmes", -"entr'assassinant", -"entr'assassinassent", -"entr'assassinassiez", -"entr'assassinassions", -"entr'assassinât", -"entr'assassinâtes", -"entr'assassine", -"entr'assassiné", -"entr'assassinées", -"entr'assassinent", -"entr'assassiner", -"entr'assassiner", -"entr'assassinera", -"entr'assassineraient", -"entr'assassinerait", -"entr'assassinèrent", -"entr'assassinerez", -"entr'assassineriez", -"entr'assassinerions", -"entr'assassinerons", -"entr'assassineront", -"entr'assassinés", -"entr'assassinez", -"entr'assassiniez", -"entr'assassinions", -"entr'assassinons", -"entr'assigna", -"entr'assignaient", -"entr'assignait", -"entr'assignâmes", -"entr'assignant", -"entr'assignassent", -"entr'assignassiez", -"entr'assignassions", -"entr'assignât", -"entr'assignâtes", -"entr'assigne", -"entr'assigné", -"entr'assignées", -"entr'assignent", -"entr'assigner", -"entr'assigner", -"entr'assignera", -"entr'assigneraient", -"entr'assignerait", -"entr'assignèrent", -"entr'assignerez", -"entr'assigneriez", -"entr'assignerions", -"entr'assignerons", -"entr'assigneront", -"entr'assignés", -"entr'assignez", -"entr'assigniez", -"entr'assignions", -"entr'assignons", -"entr'assomma", -"entr'assommaient", -"entr'assommait", -"entr'assommâmes", -"entr'assommant", -"entr'assommassent", -"entr'assommassiez", -"entr'assommassions", -"entr'assommât", -"entr'assommâtes", -"entr'assomme", -"entr'assommé", -"entr'assommées", -"entr'assomment", -"entr'assommer", -"entr'assommer", -"entr'assommera", -"entr'assommeraient", -"entr'assommerait", -"entr'assommèrent", -"entr'assommerez", -"entr'assommeriez", -"entr'assommerions", -"entr'assommerons", -"entr'assommeront", -"entr'assommés", -"entr'assommez", -"entr'assommiez", -"entr'assommions", -"entr'assommons", -"entr'attaqua", -"entr'attaquaient", -"entr'attaquait", -"entr'attaquâmes", -"entr'attaquant", -"entr'attaquassent", -"entr'attaquassiez", -"entr'attaquassions", -"entr'attaquât", -"entr'attaquâtes", -"entr'attaque", -"entr'attaqué", -"entr'attaquées", -"entr'attaquent", -"entr'attaquer", -"entr'attaquer", -"entr'attaquera", -"entr'attaqueraient", -"entr'attaquerait", -"entr'attaquèrent", -"entr'attaquerez", -"entr'attaqueriez", -"entr'attaquerions", -"entr'attaquerons", -"entr'attaqueront", -"entr'attaqués", -"entr'attaquez", -"entr'attaquiez", -"entr'attaquions", -"entr'attaquons", -"entr'attend", -"entr'attendaient", -"entr'attendait", -"entr'attendant", -"entr'attende", -"entr'attendent", -"entr'attendez", -"entr'attendiez", -"entr'attendîmes", -"entr'attendions", -"entr'attendirent", -"entr'attendissent", -"entr'attendissiez", -"entr'attendissions", -"entr'attendit", -"entr'attendît", -"entr'attendîtes", -"entr'attendons", -"entr'attendra", -"entr'attendraient", -"entr'attendrait", -"entr'attendre", -"entr'attendre", -"entr'attendrez", -"entr'attendriez", -"entr'attendrions", -"entr'attendrons", -"entr'attendront", -"entr'attendu", -"entr'attendue", -"entr'attendues", -"entr'attendus", -"entr'autres", -"entr'averti", -"entr'averties", -"entr'avertîmes", -"entr'avertir", -"entr'avertir", -"entr'avertira", -"entr'avertiraient", -"entr'avertirait", -"entr'avertirent", -"entr'avertirez", -"entr'avertiriez", -"entr'avertirions", -"entr'avertirons", -"entr'avertiront", -"entr'avertis", -"entr'avertissaient", -"entr'avertissait", -"entr'avertissant", -"entr'avertisse", -"entr'avertissent", -"entr'avertissez", -"entr'avertissiez", -"entr'avertissions", -"entr'avertissons", -"entr'avertit", -"entr'avertît", -"entr'avertîtes", -"entr'avoua", -"entr'avouaient", -"entr'avouait", -"entr'avouâmes", -"entr'avouant", -"entr'avouassent", -"entr'avouassiez", -"entr'avouassions", -"entr'avouât", -"entr'avouâtes", -"entr'avoue", -"entr'avoué", -"entr'avouées", -"entr'avouent", -"entr'avouer", -"entr'avouer", -"entr'avouera", -"entr'avoueraient", -"entr'avouerait", -"entr'avouèrent", -"entr'avouerez", -"entr'avoueriez", -"entr'avouerions", -"entr'avouerons", -"entr'avoueront", -"entr'avoués", -"entr'avouez", -"entr'avouiez", -"entr'avouions", -"entr'avouons", -"entr'axe", -"entr'axes", "Entraygues-sur-Truyère", -"entr'ébranla", -"entr'ébranlaient", -"entr'ébranlait", -"entr'ébranlâmes", -"entr'ébranlant", -"entr'ébranlassent", -"entr'ébranlassiez", -"entr'ébranlassions", -"entr'ébranlât", -"entr'ébranlâtes", -"entr'ébranle", -"entr'ébranlé", -"entr'ébranlées", -"entr'ébranlent", -"entr'ébranler", -"entr'ébranlera", -"entr'ébranleraient", -"entr'ébranlerait", -"entr'ébranlèrent", -"entr'ébranlerez", -"entr'ébranleriez", -"entr'ébranlerions", -"entr'ébranlerons", -"entr'ébranleront", -"entr'ébranlés", -"entr'ébranlez", -"entr'ébranliez", -"entr'ébranlions", -"entr'ébranlons", -"entr'éclairci", -"entr'éclaircies", -"entr'éclaircîmes", -"entr'éclaircir", -"entr'éclaircir", -"entr'éclaircira", -"entr'éclairciraient", -"entr'éclaircirait", -"entr'éclaircirent", -"entr'éclaircirez", -"entr'éclairciriez", -"entr'éclaircirions", -"entr'éclaircirons", -"entr'éclairciront", -"entr'éclaircis", -"entr'éclaircissaient", -"entr'éclaircissait", -"entr'éclaircissant", -"entr'éclaircisse", -"entr'éclaircissent", -"entr'éclaircissez", -"entr'éclaircissiez", -"entr'éclaircissions", -"entr'éclaircissons", -"entr'éclaircit", -"entr'éclaircît", -"entr'éclaircîtes", -"entr'éclore", -"entr'éclose", -"entr'écouta", -"entr'écoutaient", -"entr'écoutait", -"entr'écoutâmes", -"entr'écoutant", -"entr'écoutassent", -"entr'écoutassiez", -"entr'écoutassions", -"entr'écoutât", -"entr'écoutâtes", -"entr'écoute", -"entr'écouté", -"entr'écoutées", -"entr'écoutent", -"entr'écouter", -"entr'écoutera", -"entr'écouteraient", -"entr'écouterait", -"entr'écoutèrent", -"entr'écouterez", -"entr'écouteriez", -"entr'écouterions", -"entr'écouterons", -"entr'écouteront", -"entr'écoutés", -"entr'écoutez", -"entr'écoutiez", -"entr'écoutions", -"entr'écoutons", -"entr'écrasa", -"entr'écrasai", -"entr'écrasaient", -"entr'écrasais", -"entr'écrasait", -"entr'écrasâmes", -"entr'écrasant", -"entr'écrasas", -"entr'écrasasse", -"entr'écrasassent", -"entr'écrasasses", -"entr'écrasassiez", -"entr'écrasassions", -"entr'écrasât", -"entr'écrasâtes", -"entr'écrase", -"entr'écrasé", -"entr'écrasée", -"entr'écrasées", -"entr'écrasent", -"entr'écraser", -"entr'écraser", -"entr'écrasera", -"entr'écraserai", -"entr'écraseraient", -"entr'écraserais", -"entr'écraserait", -"entr'écraseras", -"entr'écrasèrent", -"entr'écraserez", -"entr'écraseriez", -"entr'écraserions", -"entr'écraserons", -"entr'écraseront", -"entr'écrases", -"entr'écrasés", -"entr'écrasez", -"entr'écrasiez", -"entr'écrasions", -"entr'écrasons", -"entr'écrira", -"entr'écriraient", -"entr'écrirait", -"entr'écrire", -"entr'écrire", -"entr'écrirez", -"entr'écririez", -"entr'écririons", -"entr'écrirons", -"entr'écriront", -"entr'écrit", -"entr'écrite", -"entr'écrites", -"entr'écrits", -"entr'écrivaient", -"entr'écrivait", -"entr'écrivant", -"entr'écrive", -"entr'écrivent", -"entr'écrivez", -"entr'écriviez", -"entr'écrivîmes", -"entr'écrivions", -"entr'écrivirent", -"entr'écrivissent", -"entr'écrivissions", -"entr'écrivit", -"entr'écrivît", -"entr'écrivîtes", -"entr'écrivons", -"entrée-sortie", -"entrées-sorties", -"entr'égorge", -"entr'égorgé", -"entr'égorgea", -"entr'égorgeai", -"entr'égorgeaient", -"entr'égorgeait", -"entr'égorgeâmes", -"entr'égorgeant", -"entr'égorgeassent", -"entr'égorgeassiez", -"entr'égorgeassions", -"entr'égorgeât", -"entr'égorgeâtes", -"entr'égorgée", -"entr'égorgées", -"entr'égorgemens", -"entr'égorgement", -"entr'égorgements", -"entr'égorgent", -"entr'égorgeons", -"entr'égorger", -"entr'égorger", -"entr'égorgera", -"entr'égorgeraient", -"entr'égorgerait", -"entr'égorgèrent", -"entr'égorgerez", -"entr'égorgeriez", -"entr'égorgerions", -"entr'égorgerons", -"entr'égorgeront", -"entr'égorges", -"entr'égorgés", -"entr'égorgez", -"entr'égorgiez", -"entr'égorgions", -"entr'égratigna", -"entr'égratignaient", -"entr'égratignait", -"entr'égratignâmes", -"entr'égratignant", -"entr'égratignassent", -"entr'égratignassiez", -"entr'égratignassions", -"entr'égratignât", -"entr'égratignâtes", -"entr'égratigne", -"entr'égratigné", -"entr'égratignées", -"entr'égratignent", -"entr'égratigner", -"entr'égratigner", -"entr'égratignera", -"entr'égratigneraient", -"entr'égratignerait", -"entr'égratignèrent", -"entr'égratignerez", -"entr'égratigneriez", -"entr'égratignerions", -"entr'égratignerons", -"entr'égratigneront", -"entr'égratignés", -"entr'égratignez", -"entr'égratigniez", -"entr'égratignions", -"entr'égratignons", -"entr'embarrassa", -"entr'embarrassaient", -"entr'embarrassait", -"entr'embarrassâmes", -"entr'embarrassant", -"entr'embarrassassent", -"entr'embarrassassiez", -"entr'embarrassassions", -"entr'embarrassât", -"entr'embarrassâtes", -"entr'embarrasse", -"entr'embarrassé", -"entr'embarrassées", -"entr'embarrassent", -"entr'embarrasser", -"entr'embarrasser", -"entr'embarrassera", -"entr'embarrasseraient", -"entr'embarrasserait", -"entr'embarrassèrent", -"entr'embarrasserez", -"entr'embarrasseriez", -"entr'embarrasserions", -"entr'embarrasserons", -"entr'embarrasseront", -"entr'embarrassés", -"entr'embarrassez", -"entr'embarrassiez", -"entr'embarrassions", -"entr'embarrassons", -"entr'embrassa", -"entr'embrassaient", -"entr'embrassait", -"entr'embrassâmes", -"entr'embrassant", -"entr'embrassassent", -"entr'embrassassiez", -"entr'embrassassions", -"entr'embrassât", -"entr'embrassâtes", -"entr'embrasse", -"entr'embrassé", -"entr'embrassées", -"entr'embrassent", -"entr'embrasser", -"entr'embrasser", -"entr'embrassera", -"entr'embrasseraient", -"entr'embrasserait", -"entr'embrassèrent", -"entr'embrasserez", -"entr'embrasseriez", -"entr'embrasserions", -"entr'embrasserons", -"entr'embrasseront", -"entr'embrassés", -"entr'embrassez", -"entr'embrassiez", -"entr'embrassions", -"entr'embrassons", +"Entre-Deux", +"Entre-deux-Eaux", +"Entre-deux-Guiers", +"Entre-deux-Monts", "Entremont-le-Vieux", -"entr'empêcha", -"entr'empêchaient", -"entr'empêchait", -"entr'empêchâmes", -"entr'empêchant", -"entr'empêchassent", -"entr'empêchassiez", -"entr'empêchassions", -"entr'empêchât", -"entr'empêchâtes", -"entr'empêche", -"entr'empêché", -"entr'empêchées", -"entr'empêchent", -"entr'empêcher", -"entr'empêcher", -"entr'empêchera", -"entr'empêcheraient", -"entr'empêcherait", -"entr'empêchèrent", -"entr'empêcherez", -"entr'empêcheriez", -"entr'empêcherions", -"entr'empêcherons", -"entr'empêcheront", -"entr'empêchés", -"entr'empêchez", -"entr'empêchiez", -"entr'empêchions", -"entr'empêchons", -"entr'encourage", -"entr'encouragé", -"entr'encouragea", -"entr'encourageaient", -"entr'encourageait", -"entr'encourageâmes", -"entr'encourageant", -"entr'encourageassent", -"entr'encourageassiez", -"entr'encourageassions", -"entr'encourageât", -"entr'encourageâtes", -"entr'encouragées", -"entr'encouragent", -"entr'encourageons", -"entr'encourager", -"entr'encourager", -"entr'encouragera", -"entr'encourageraient", -"entr'encouragerait", -"entr'encouragèrent", -"entr'encouragerez", -"entr'encourageriez", -"entr'encouragerions", -"entr'encouragerons", -"entr'encourageront", -"entr'encouragés", -"entr'encouragez", -"entr'encouragiez", -"entr'encouragions", -"entr'enleva", -"entr'enlevaient", -"entr'enlevait", -"entr'enlevâmes", -"entr'enlevant", -"entr'enlevassent", -"entr'enlevassiez", -"entr'enlevassions", -"entr'enlevât", -"entr'enlevâtes", -"entr'enlève", -"entr'enlevé", -"entr'enlevées", -"entr'enlèvent", -"entr'enlever", -"entr'enlever", -"entr'enlèvera", -"entr'enlèveraient", -"entr'enlèverait", -"entr'enlevèrent", -"entr'enlèverez", -"entr'enlèveriez", -"entr'enlèverions", -"entr'enlèverons", -"entr'enlèveront", -"entr'enlevés", -"entr'enlevez", -"entr'enleviez", -"entr'enlevions", -"entr'enlevons", -"entr'entend", -"entr'entendaient", -"entr'entendait", -"entr'entendant", -"entr'entende", -"entr'entendent", -"entr'entendez", -"entr'entendiez", -"entr'entendîmes", -"entr'entendions", -"entr'entendirent", -"entr'entendissent", -"entr'entendissiez", -"entr'entendissions", -"entr'entendit", -"entr'entendît", -"entr'entendîtes", -"entr'entendons", -"entr'entendra", -"entr'entendraient", -"entr'entendrait", -"entr'entendre", -"entr'entendre", -"entr'entendrez", -"entr'entendriez", -"entr'entendrions", -"entr'entendrons", -"entr'entendront", -"entr'entendu", -"entr'entendue", -"entr'entendues", -"entr'entendus", -"entr'enverra", -"entr'enverrai", -"entr'enverraient", -"entr'enverrais", -"entr'enverrait", -"entr'enverras", -"entr'enverrez", -"entr'enverriez", -"entr'enverrions", -"entr'enverrons", -"entr'enverront", -"entr'envoie", -"entr'envoient", -"entr'envoies", -"entr'envoya", -"entr'envoyai", -"entr'envoyaient", -"entr'envoyais", -"entr'envoyait", -"entr'envoyâmes", -"entr'envoyant", -"entr'envoyas", -"entr'envoyasse", -"entr'envoyassent", -"entr'envoyasses", -"entr'envoyassiez", -"entr'envoyassions", -"entr'envoyât", -"entr'envoyâtes", -"entr'envoyé", -"entr'envoyée", -"entr'envoyées", -"entr'envoyer", -"entr'envoyer", -"entr'envoyèrent", -"entr'envoyés", -"entr'envoyez", -"entr'envoyiez", -"entr'envoyions", -"entr'envoyons", -"entr'épia", -"entr'épiaient", -"entr'épiait", -"entr'épiâmes", -"entr'épiant", -"entr'épiassent", -"entr'épiassiez", -"entr'épiassions", -"entr'épiât", -"entr'épiâtes", -"entr'épie", -"entr'épié", -"entr'épiées", -"entr'épient", -"entr'épier", -"entr'épier", -"entr'épiera", -"entr'épieraient", -"entr'épierait", -"entr'épièrent", -"entr'épierez", -"entr'épieriez", -"entr'épierions", -"entr'épierons", -"entr'épieront", -"entr'épiés", -"entr'épiez", -"entr'épiiez", -"entr'épiions", -"entr'épions", -"entr'éprouva", -"entr'éprouvaient", -"entr'éprouvait", -"entr'éprouvâmes", -"entr'éprouvant", -"entr'éprouvassent", -"entr'éprouvassiez", -"entr'éprouvassions", -"entr'éprouvât", -"entr'éprouvâtes", -"entr'éprouve", -"entr'éprouvé", -"entr'éprouvées", -"entr'éprouvent", -"entr'éprouver", -"entr'éprouver", -"entr'éprouvera", -"entr'éprouveraient", -"entr'éprouverait", -"entr'éprouvèrent", -"entr'éprouverez", -"entr'éprouveriez", -"entr'éprouverions", -"entr'éprouverons", -"entr'éprouveront", -"entr'éprouvés", -"entr'éprouvez", -"entr'éprouviez", -"entr'éprouvions", -"entr'éprouvons", -"entrer-coucher", -"entr'escroqua", -"entr'escroquaient", -"entr'escroquait", -"entr'escroquâmes", -"entr'escroquant", -"entr'escroquassent", -"entr'escroquassiez", -"entr'escroquassions", -"entr'escroquât", -"entr'escroquâtes", -"entr'escroque", -"entr'escroqué", -"entr'escroquées", -"entr'escroquent", -"entr'escroquer", -"entr'escroquer", -"entr'escroquera", -"entr'escroqueraient", -"entr'escroquerait", -"entr'escroquèrent", -"entr'escroquerez", -"entr'escroqueriez", -"entr'escroquerions", -"entr'escroquerons", -"entr'escroqueront", -"entr'escroqués", -"entr'escroquez", -"entr'escroquiez", -"entr'escroquions", -"entr'escroquons", -"entr'étouffa", -"entr'étouffaient", -"entr'étouffait", -"entr'étouffâmes", -"entr'étouffant", -"entr'étouffassent", -"entr'étouffassiez", -"entr'étouffassions", -"entr'étouffât", -"entr'étouffâtes", -"entr'étouffe", -"entr'étouffé", -"entr'étouffées", -"entr'étouffent", -"entr'étouffer", -"entr'étouffer", -"entr'étouffera", -"entr'étoufferaient", -"entr'étoufferait", -"entr'étouffèrent", -"entr'étoufferez", -"entr'étoufferiez", -"entr'étoufferions", -"entr'étoufferons", -"entr'étoufferont", -"entr'étouffés", -"entr'étouffez", -"entr'étouffiez", -"entr'étouffions", -"entr'étouffons", -"entr'étripa", -"entr'étripaient", -"entr'étripait", -"entr'étripâmes", -"entr'étripant", -"entr'étripassent", -"entr'étripassiez", -"entr'étripassions", -"entr'étripât", -"entr'étripâtes", -"entr'étripe", -"entr'étripé", -"entr'étripées", -"entr'étripent", -"entr'étriper", -"entr'étriper", -"entr'étripera", -"entr'étriperaient", -"entr'étriperait", -"entr'étripèrent", -"entr'étriperez", -"entr'étriperiez", -"entr'étriperions", -"entr'étriperons", -"entr'étriperont", -"entr'étripés", -"entr'étripez", -"entr'étripiez", -"entr'étripions", -"entr'étripons", -"entr'eux", -"entr'éveilla", -"entr'éveillaient", -"entr'éveillait", -"entr'éveillâmes", -"entr'éveillant", -"entr'éveillassent", -"entr'éveillassiez", -"entr'éveillassions", -"entr'éveillât", -"entr'éveillâtes", -"entr'éveille", -"entr'éveillé", -"entr'éveillées", -"entr'éveillent", -"entr'éveiller", -"entr'éveiller", -"entr'éveillera", -"entr'éveilleraient", -"entr'éveillerait", -"entr'éveillèrent", -"entr'éveillerez", -"entr'éveilleriez", -"entr'éveillerions", -"entr'éveillerons", -"entr'éveilleront", -"entr'éveillés", -"entr'éveillez", -"entr'éveilliez", -"entr'éveillions", -"entr'éveillons", -"entr'excita", -"entr'excitaient", -"entr'excitait", -"entr'excitâmes", -"entr'excitant", -"entr'excitassent", -"entr'excitassiez", -"entr'excitassions", -"entr'excitât", -"entr'excitâtes", -"entr'excite", -"entr'excité", -"entr'excitées", -"entr'excitent", -"entr'exciter", -"entr'exciter", -"entr'excitera", -"entr'exciteraient", -"entr'exciterait", -"entr'excitèrent", -"entr'exciterez", -"entr'exciteriez", -"entr'exciterions", -"entr'exciterons", -"entr'exciteront", -"entr'excités", -"entr'excitez", -"entr'excitiez", -"entr'excitions", -"entr'excitons", -"entr'exhorta", -"entr'exhortaient", -"entr'exhortait", -"entr'exhortâmes", -"entr'exhortant", -"entr'exhortassent", -"entr'exhortassiez", -"entr'exhortassions", -"entr'exhortât", -"entr'exhortâtes", -"entr'exhorte", -"entr'exhorté", -"entr'exhortées", -"entr'exhortent", -"entr'exhorter", -"entr'exhorter", -"entr'exhortera", -"entr'exhorteraient", -"entr'exhorterait", -"entr'exhortèrent", -"entr'exhorterez", -"entr'exhorteriez", -"entr'exhorterions", -"entr'exhorterons", -"entr'exhorteront", -"entr'exhortés", -"entr'exhortez", -"entr'exhortiez", -"entr'exhortions", -"entr'exhortons", -"entr'hiver", -"entr'hiverna", -"entr'hivernai", -"entr'hivernaient", -"entr'hivernais", -"entr'hivernait", -"entr'hivernâmes", -"entr'hivernant", -"entr'hivernas", -"entr'hivernasse", -"entr'hivernassent", -"entr'hivernasses", -"entr'hivernassiez", -"entr'hivernassions", -"entr'hivernât", -"entr'hivernâtes", -"entr'hiverne", -"entr'hiverné", -"entr'hivernée", -"entr'hivernées", -"entr'hivernent", -"entr'hiverner", -"entr'hivernera", -"entr'hivernerai", -"entr'hiverneraient", -"entr'hivernerais", -"entr'hivernerait", -"entr'hiverneras", -"entr'hivernèrent", -"entr'hivernerez", -"entr'hiverneriez", -"entr'hivernerions", -"entr'hivernerons", -"entr'hiverneront", -"entr'hivernes", -"entr'hivernés", -"entr'hivernez", -"entr'hiverniez", -"entr'hivernions", -"entr'hivernons", -"entr'honora", -"entr'honoraient", -"entr'honorait", -"entr'honorâmes", -"entr'honorant", -"entr'honorassent", -"entr'honorassiez", -"entr'honorassions", -"entr'honorât", -"entr'honorâtes", -"entr'honore", -"entr'honoré", -"entr'honorées", -"entr'honorent", -"entr'honorer", -"entr'honorer", -"entr'honorera", -"entr'honoreraient", -"entr'honorerait", -"entr'honorèrent", -"entr'honorerez", -"entr'honoreriez", -"entr'honorerions", -"entr'honorerons", -"entr'honoreront", -"entr'honorés", -"entr'honorez", -"entr'honoriez", -"entr'honorions", -"entr'honorons", -"entr'immola", -"entr'immolaient", -"entr'immolait", -"entr'immolâmes", -"entr'immolant", -"entr'immolassent", -"entr'immolassiez", -"entr'immolassions", -"entr'immolât", -"entr'immolâtes", -"entr'immole", -"entr'immolé", -"entr'immolées", -"entr'immolent", -"entr'immoler", -"entr'immoler", -"entr'immolera", -"entr'immoleraient", -"entr'immolerait", -"entr'immolèrent", -"entr'immolerez", -"entr'immoleriez", -"entr'immolerions", -"entr'immolerons", -"entr'immoleront", -"entr'immolés", -"entr'immolez", -"entr'immoliez", -"entr'immolions", -"entr'immolons", -"entr'incommoda", -"entr'incommodaient", -"entr'incommodait", -"entr'incommodâmes", -"entr'incommodant", -"entr'incommodassent", -"entr'incommodassiez", -"entr'incommodassions", -"entr'incommodât", -"entr'incommodâtes", -"entr'incommode", -"entr'incommodé", -"entr'incommodées", -"entr'incommodent", -"entr'incommoder", -"entr'incommoder", -"entr'incommodera", -"entr'incommoderaient", -"entr'incommoderait", -"entr'incommodèrent", -"entr'incommoderez", -"entr'incommoderiez", -"entr'incommoderions", -"entr'incommoderons", -"entr'incommoderont", -"entr'incommodés", -"entr'incommodez", -"entr'incommodiez", -"entr'incommodions", -"entr'incommodons", -"entr'injuria", -"entr'injuriaient", -"entr'injuriait", -"entr'injuriâmes", -"entr'injuriant", -"entr'injuriassent", -"entr'injuriassiez", -"entr'injuriassions", -"entr'injuriât", -"entr'injuriâtes", -"entr'injurie", -"entr'injurié", -"entr'injuriées", -"entr'injurient", -"entr'injurier", -"entr'injurier", -"entr'injuriera", -"entr'injurieraient", -"entr'injurierait", -"entr'injurièrent", -"entr'injurierez", -"entr'injurieriez", -"entr'injurierions", -"entr'injurierons", -"entr'injurieront", -"entr'injuriés", -"entr'injuriez", -"entr'injuriiez", -"entr'injuriions", -"entr'injurions", -"entr'instruira", -"entr'instruiraient", -"entr'instruirait", -"entr'instruire", -"entr'instruire", -"entr'instruirez", -"entr'instruiriez", -"entr'instruirions", -"entr'instruirons", -"entr'instruiront", -"entr'instruisaient", -"entr'instruisait", -"entr'instruisant", -"entr'instruise", -"entr'instruisent", -"entr'instruisez", -"entr'instruisiez", -"entr'instruisîmes", -"entr'instruisions", -"entr'instruisirent", -"entr'instruisissent", -"entr'instruisissions", -"entr'instruisit", -"entr'instruisît", -"entr'instruisîtes", -"entr'instruisons", -"entr'instruit", -"entr'instruite", -"entr'instruites", -"entr'instruits", -"entr'oblige", -"entr'obligé", -"entr'obligea", -"entr'obligeaient", -"entr'obligeait", -"entr'obligeâmes", -"entr'obligeant", -"entr'obligeassent", -"entr'obligeassiez", -"entr'obligeassions", -"entr'obligeât", -"entr'obligeâtes", -"entr'obligées", -"entr'obligent", -"entr'obligeons", -"entr'obliger", -"entr'obliger", -"entr'obligera", -"entr'obligeraient", -"entr'obligerait", -"entr'obligèrent", -"entr'obligerez", -"entr'obligeriez", -"entr'obligerions", -"entr'obligerons", -"entr'obligeront", -"entr'obligés", -"entr'obligez", -"entr'obligiez", -"entr'obligions", -"entr'offensa", -"entr'offensaient", -"entr'offensait", -"entr'offensâmes", -"entr'offensant", -"entr'offensassent", -"entr'offensassiez", -"entr'offensassions", -"entr'offensât", -"entr'offensâtes", -"entr'offense", -"entr'offensé", -"entr'offensées", -"entr'offensent", -"entr'offenser", -"entr'offenser", -"entr'offensera", -"entr'offenseraient", -"entr'offenserait", -"entr'offensèrent", -"entr'offenserez", -"entr'offenseriez", -"entr'offenserions", -"entr'offenserons", -"entr'offenseront", -"entr'offensés", -"entr'offensez", -"entr'offensiez", -"entr'offensions", -"entr'offensons", -"entr'oie", -"entr'oient", -"entr'oies", -"entr'ois", -"entr'oit", -"entr'ombrage", -"entr'ombragé", -"entr'ombragea", -"entr'ombrageaient", -"entr'ombrageait", -"entr'ombrageâmes", -"entr'ombrageant", -"entr'ombrageassent", -"entr'ombrageassiez", -"entr'ombrageassions", -"entr'ombrageât", -"entr'ombrageâtes", -"entr'ombragées", -"entr'ombragent", -"entr'ombrageons", -"entr'ombrager", -"entr'ombrager", -"entr'ombragera", -"entr'ombrageraient", -"entr'ombragerait", -"entr'ombragèrent", -"entr'ombragerez", -"entr'ombrageriez", -"entr'ombragerions", -"entr'ombragerons", -"entr'ombrageront", -"entr'ombragés", -"entr'ombragez", -"entr'ombragiez", -"entr'ombragions", -"entr'opercule", -"entr'orraient", -"entr'orrais", -"entr'orrait", -"entr'orriez", -"entr'orrions", -"entr'oublia", -"entr'oubliaient", -"entr'oubliait", -"entr'oubliâmes", -"entr'oubliant", -"entr'oubliassent", -"entr'oubliassiez", -"entr'oubliassions", -"entr'oubliât", -"entr'oubliâtes", -"entr'oublie", -"entr'oublié", -"entr'oubliées", -"entr'oublient", -"entr'oublier", -"entr'oublier", -"entr'oubliera", -"entr'oublieraient", -"entr'oublierait", -"entr'oublièrent", -"entr'oublierez", -"entr'oublieriez", -"entr'oublierions", -"entr'oublierons", -"entr'oublieront", -"entr'oubliés", -"entr'oubliez", -"entr'oubliiez", -"entr'oubliions", -"entr'oublions", -"entr'ouï", -"entr'ouïe", -"entr'ouïes", -"entr'ouïmes", -"entr'ouïr", -"entr'ouïra", -"entr'ouïrai", -"entr'ouïraient", -"entr'ouïrais", -"entr'ouïrait", -"entr'ouïras", -"entr'ouïrent", -"entr'ouïrez", -"entr'ouïriez", -"entr'ouïrions", -"entr'ouïrons", -"entr'ouïront", -"entr'ouïs", -"entr'ouïsse", -"entr'ouïssent", -"entr'ouïsses", -"entr'ouïssiez", -"entr'ouïssions", -"entr'ouït", -"entr'ouïtes", -"entr'outrage", -"entr'outragé", -"entr'outragea", -"entr'outrageaient", -"entr'outrageait", -"entr'outrageâmes", -"entr'outrageant", -"entr'outrageassent", -"entr'outrageassiez", -"entr'outrageassions", -"entr'outrageât", -"entr'outrageâtes", -"entr'outragées", -"entr'outragent", -"entr'outrageons", -"entr'outrager", -"entr'outrager", -"entr'outragera", -"entr'outrageraient", -"entr'outragerait", -"entr'outragèrent", -"entr'outragerez", -"entr'outrageriez", -"entr'outragerions", -"entr'outragerons", -"entr'outrageront", -"entr'outragés", -"entr'outragez", -"entr'outragiez", -"entr'outragions", -"entr'ouvert", -"entr'ouverte", -"entr'ouvertes", -"entr'ouverts", -"entr'ouverture", -"entr'ouvraient", -"entr'ouvrais", -"entr'ouvrait", -"entr'ouvrant", -"entr'ouvre", -"entr'ouvrent", -"entr'ouvres", -"entr'ouvrez", -"entr'ouvriez", -"entr'ouvrîmes", -"entr'ouvrions", -"entr'ouvrir", -"entr'ouvrir", -"entr'ouvrira", -"entr'ouvrirai", -"entr'ouvriraient", -"entr'ouvrirais", -"entr'ouvrirait", -"entr'ouvriras", -"entr'ouvrirent", -"entr'ouvrirez", -"entr'ouvririez", -"entr'ouvririons", -"entr'ouvrirons", -"entr'ouvriront", -"entr'ouvris", -"entr'ouvrisse", -"entr'ouvrissent", -"entr'ouvrisses", -"entr'ouvrissiez", -"entr'ouvrissions", -"entr'ouvrit", -"entr'ouvrît", -"entr'ouvrîtes", -"entr'ouvrons", -"entr'oyaient", -"entr'oyais", -"entr'oyait", -"entr'oyant", -"entr'oyez", -"entr'oyiez", -"entr'oyions", -"entr'oyons", -"entr'usa", -"entr'usaient", -"entr'usait", -"entr'usâmes", -"entr'usant", -"entr'usassent", -"entr'usassiez", -"entr'usassions", -"entr'usât", -"entr'usâtes", -"entr'use", -"entr'usé", -"entr'usées", -"entr'usent", -"entr'user", -"entr'user", -"entr'usera", -"entr'useraient", -"entr'userait", -"entr'usèrent", -"entr'userez", -"entr'useriez", -"entr'userions", -"entr'userons", -"entr'useront", -"entr'usés", -"entr'usez", -"entr'usiez", -"entr'usions", -"entr'usons", -"Éole-en-Beauce", -"éoli-harpe", +"Eole-en-Beauce", "Epagne-Epagnette", -"Épagne-Épagnette", -"épargne-logement", -"épaulé-jeté", -"épaulés-jetés", +"Epagny Metz-Tessy", "Epaux-Bézu", -"Épaux-Bézu", "Epeigné-les-Bois", -"Épeigné-les-Bois", "Epeigné-sur-Dême", -"Épeigné-sur-Dême", "Epercieux-Saint-Paul", -"Épercieux-Saint-Paul", "Epernay-sous-Gevrey", -"Épernay-sous-Gevrey", -"Epiais-lès-Louvres", -"Épiais-lès-Louvres", -"Epiais-Rhus", -"Épiais-Rhus", "Epi-Contois", -"épi-contois", -"Épi-Contois", "Epi-Contoise", -"épi-contoise", -"Épi-Contoise", "Epi-Contoises", -"épi-contoises", -"Épi-Contoises", -"épidote-gris", +"Epiais-Rhus", +"Epiais-lès-Louvres", "Epieds-en-Beauce", -"Épieds-en-Beauce", "Epiez-sur-Chiers", -"Épiez-sur-Chiers", "Epiez-sur-Meuse", -"Épiez-sur-Meuse", -"Épinac-les-Mines", -"épinard-fraise", "Epinay-Champlâtreux", -"Épinay-Champlâtreux", "Epinay-le-Comte", -"Épinay-le-Comte", "Epinay-sous-Sénart", -"Épinay-sous-Sénart", "Epinay-sur-Duclair", -"Épinay-sur-Duclair", "Epinay-sur-Odon", -"Épinay-sur-Odon", "Epinay-sur-Orge", -"Épinay-sur-Orge", "Epinay-sur-Seine", -"Épinay-sur-Seine", -"Epineau-les-Voves", -"Épineau-les-Voves", "Epine-aux-Bois", -"Épine-aux-Bois", -"épine-du-Christ", -"épine-fleurie", -"épines-vinettes", -"Epineuil-le-Fleuriel", -"Épineuil-le-Fleuriel", +"Epineau-les-Voves", "Epineu-le-Chevreuil", -"Épineu-le-Chevreuil", +"Epineuil-le-Fleuriel", "Epineux-le-Seguin", -"Épineux-le-Seguin", -"épine-vinette", -"épiplo-entérocèle", -"épiplo-ischiocèle", -"épiplo-mérocèle", -"épluche-légume", -"épluche-légumes", -"Eppenberg-Wöschnau", "Eppe-Sauvage", +"Eppenberg-Wöschnau", "Epreville-en-Lieuvin", -"Épreville-en-Lieuvin", "Epreville-en-Roumois", -"Épreville-en-Roumois", "Epreville-près-le-Neubourg", -"Épreville-près-le-Neubourg", -"e-procurement", -"e-procurements", -"ep's", -"épuises-volantes", -"épuise-volante", -"équato-guinéen", -"équato-guinéenne", -"équato-guinéennes", -"équato-guinéens", -"Équatoria-Central", -"Équatoria-Occidental", -"Équatoria-Oriental", "Equennes-Eramecourt", -"Équennes-Éramecourt", "Equeurdreville-Hainneville", -"Équeurdreville-Hainneville", "Equihen-Plage", -"Équihen-Plage", "Eragny-sur-Epte", -"Éragny-sur-Epte", -"Éragny-sur-Oise", "Erbes-Büdesheim", "Erbéviller-sur-Amezule", "Ercé-en-Lamée", "Ercé-près-Liffré", "Erdre-en-Anjou", -"e-reader", -"e-readers", -"e-réputation", -"e-réputations", -"e-réservation", -"e-réservations", "Ergué-Armel", "Ergué-Gabéric", -"Erize-la-Brûlée", -"Érize-la-Brûlée", -"Érize-la-Grande", -"Erize-la-Petite", -"Érize-la-Petite", "Erize-Saint-Dizier", -"Érize-Saint-Dizier", +"Erize-la-Brûlée", +"Erize-la-Petite", "Erlangen-Höchstadt", "Erlbach-Kirchberg", "Ermenonville-la-Grande", @@ -9853,20 +4351,19 @@ FR_BASE_EXCEPTIONS = [ "Ernemont-sur-Buchy", "Erneville-aux-Bois", "Ernolsheim-Bruche", -"Ernolsheim-lès-Saverne", "Ernolsheim-Saverne", +"Ernolsheim-lès-Saverne", "Erny-Saint-Julien", "Erpe-Mere", "Erps-Kwerps", -"Erquinghem-le-Sec", "Erquinghem-Lys", +"Erquinghem-le-Sec", "Ervy-le-Châtel", -"e-santé", "Esboz-Brest", -"Eschbach-au-Val", -"Eschêne-Autrage", "Esch-sur-Alzette", "Esch-sur-Sûre", +"Eschbach-au-Val", +"Eschêne-Autrage", "Esclassan-Labastide", "Esclavolles-Lurey", "Escles-Saint-Pierre", @@ -9876,59 +4373,35 @@ FR_BASE_EXCEPTIONS = [ "Escry-le-Franc", "Escueillens-et-Saint-Just-de-Bélengard", "Escures-sur-Favières", -"eskimau-aléoute", -"eskimo-aléoute", -"eskimo-aléoutes", "Eslourenties-Daban", "Esmery-Hallon", "Esnes-en-Argonne", -"éso-narthex", -"espace-boutique", -"espaces-temps", -"espaces-ventes", -"espace-temps", -"espace-vente", -"espadon-voilier", "Espagnac-Sainte-Eulalie", "Espaly-Saint-Marcel", "Esparron-de-Verdon", "Esparron-la-Bâtie", -"Espès-Undurein", "Espierres-Helchin", "Espinasse-Vozelle", "Espira-de-Conflent", "Espira-de-l'Agly", "Esplantas-Vazeilles", "Esplas-de-Sérou", -"e-sport", -"e-sportif", -"e-sportifs", -"e-sports", -"esprit-de-bois", -"esprit-de-sel", -"esprit-de-vin", -"esprit-fort", "Esprit-Saint", -"esprits-forts", +"Espès-Undurein", "Esquay-Notre-Dame", "Esquay-sur-Seulles", "Esquièze-Sère", -"esquimau-aléoute", -"esquimo-aléoute", "Essche-Saint-Liévin", +"Essert-Pittet", +"Essert-Romanais", +"Essert-Romanaise", +"Essert-Romanaises", +"Essert-Romand", "Essertenne-et-Cecey", "Essertines-en-Châtelneuf", "Essertines-en-Donzy", "Essertines-sur-Rolle", "Essertines-sur-Yverdon", -"Essert-Pittet", -"essert-romanais", -"Essert-Romanais", -"essert-romanaise", -"Essert-Romanaise", -"essert-romanaises", -"Essert-Romanaises", -"Essert-Romand", "Esserts-Blay", "Esserts-Salève", "Esserval-Combe", @@ -9936,286 +4409,73 @@ FR_BASE_EXCEPTIONS = [ "Essey-et-Maizerais", "Essey-la-Côte", "Essey-les-Eaux", -"Essey-lès-Nancy", "Essey-les-Ponts", +"Essey-lès-Nancy", "Essigny-le-Grand", "Essigny-le-Petit", -"Eßleben-Teutleben", "Essômes-sur-Marne", -"essuie-glace", -"essuie-glaces", -"essuie-main", -"essuie-mains", -"essuie-meuble", -"essuie-meubles", -"essuie-phare", -"essuie-phares", -"essuie-pied", -"essuie-pieds", -"essuie-plume", -"essuie-plumes", -"essuie-tout", -"essuie-touts", -"essuie-verre", -"essuie-verres", "Estavayer-le-Lac", "Estinnes-au-Mont", "Estinnes-au-Val", "Estouteville-Ecalles", "Estouteville-Écalles", "Estrée-Blanche", -"estrée-blanchois", "Estrée-Blanchois", -"estrée-blanchoise", "Estrée-Blanchoise", -"estrée-blanchoises", "Estrée-Blanchoises", -"estrée-cauchois", "Estrée-Cauchois", -"estrée-cauchoise", "Estrée-Cauchoise", -"estrée-cauchoises", "Estrée-Cauchoises", "Estrée-Cauchy", +"Estrée-Wamin", +"Estrée-Waminois", +"Estrée-Waminoise", +"Estrée-Waminoises", "Estrées-Deniécourt", +"Estrées-Mons", +"Estrées-Saint-Denis", "Estrées-en-Chaussée", "Estrées-la-Campagne", "Estrées-lès-Crécy", -"Estrées-Mons", -"Estrées-Saint-Denis", "Estrées-sur-Noye", -"Estrée-Wamin", -"estrée-waminois", -"Estrée-Waminois", -"estrée-waminoise", -"Estrée-Waminoise", -"estrée-waminoises", -"Estrée-Waminoises", "Esves-le-Moutier", "Etables-sur-Mer", -"Étables-sur-Mer", "Etais-la-Sauvin", -"Étais-la-Sauvin", -"étalon-or", "Etampes-sur-Marne", -"Étampes-sur-Marne", "Etang-Bertrand", -"Étang-Bertrand", -"Etang-la-Ville", -"Étang-la-Ville", "Etang-Salé", -"Étang-Salé", "Etang-Saléen", -"étang-saléen", -"Étang-Saléen", "Etang-Saléenne", -"étang-saléenne", -"Étang-Saléenne", "Etang-Saléennes", -"étang-saléennes", -"Étang-Saléennes", "Etang-Saléens", -"étang-saléens", -"Étang-Saléens", -"Etang-sur-Arroux", -"Étang-sur-Arroux", "Etang-Vergy", -"Étang-Vergy", -"état-limite", -"état-major", -"État-major", -"État-Major", -"État-nation", -"État-nounou", -"État-providence", -"états-civils", -"états-généraux", -"États-Généraux", -"états-limites", -"états-majors", -"États-majors", -"États-Majors", -"états-nations", -"États-nations", -"États-nounous", -"États-providence", -"états-unianisa", -"états-unianisai", -"états-unianisaient", -"états-unianisais", -"états-unianisait", -"états-unianisâmes", -"états-unianisant", -"états-unianisas", -"états-unianisasse", -"états-unianisassent", -"états-unianisasses", -"états-unianisassiez", -"états-unianisassions", -"états-unianisât", -"états-unianisâtes", -"états-unianise", -"états-unianisé", -"états-unianisée", -"états-unianisées", -"états-unianisent", -"états-unianiser", -"états-unianisera", -"états-unianiserai", -"états-unianiseraient", -"états-unianiserais", -"états-unianiserait", -"états-unianiseras", -"états-unianisèrent", -"états-unianiserez", -"états-unianiseriez", -"états-unianiserions", -"états-unianiserons", -"états-unianiseront", -"états-unianises", -"états-unianisés", -"états-unianisez", -"états-unianisiez", -"états-unianisions", -"états-unianisons", -"états-unien", -"États-Unien", -"états-unienne", -"États-Unienne", -"états-uniennes", -"États-Uniennes", -"états-uniens", -"États-Uniens", +"Etang-la-Ville", +"Etang-sur-Arroux", "Etats-Unis", -"États-Unis", -"étau-limeur", -"étaux-limeurs", "Etaves-et-Bocquiaux", -"Étaves-et-Bocquiaux", -"éthane-1,2-diol", -"éthéro-chloroforme", -"ethnico-religieux", -"éthyl-benzène", -"e-ticket", -"e-tickets", -"Étinehem-Méricourt", "Etival-Clairefontaine", -"Étival-Clairefontaine", "Etival-lès-le-Mans", -"Étival-lès-le-Mans", "Etoile-Saint-Cyrice", -"Étoile-Saint-Cyrice", "Etoile-sur-Rhône", -"Étoile-sur-Rhône", -"étouffe-chrétien", -"étouffe-chrétiens", -"e-tourisme", -"étrangle-chat", -"étrangle-chien", -"étrangle-loup", -"étrangle-loups", -"être-en-soi", -"être-là", "Etrelles-et-la-Montbleuse", -"Étrelles-et-la-Montbleuse", "Etrelles-sur-Aube", -"Étrelles-sur-Aube", -"êtres-en-soi", "Etricourt-Manancourt", -"Étricourt-Manancourt", "Etricourt-Manancourtois", -"étricourt-manancourtois", -"Étricourt-Manancourtois", "Etricourt-Manancourtoise", -"étricourt-manancourtoise", -"Étricourt-Manancourtoise", "Etricourt-Manancourtoises", -"étricourt-manancourtoises", -"Étricourt-Manancourtoises", "Etten-Leur", -"Étueffont-Bas", "Etxarri-Aranatz", "Eugénie-les-Bains", "Euilly-et-Lombut", "Eure-et-Loir", -"euro-africain", -"euro-africaines", "Euro-Afrique", -"euro-asiatique", -"euro-asiatiques", -"euro-bashing", -"euro-manifestation", -"euro-manifestations", -"euro-obligation", -"euro-obligations", "Eurville-Bienville", -"eusses-tu-cru", -"eux-mêmes", "Evaux-et-Ménil", -"Évaux-et-Ménil", "Evaux-les-Bains", -"Évaux-les-Bains", "Evette-Salbert", -"Évette-Salbert", "Evian-les-Bains", -"Évian-les-Bains", "Evin-Malmaison", -"Évin-Malmaison", "Evry-Grégy-sur-Yerre", -"Évry-Grégy-sur-Yerre", -"Évry-Petit-Bourg", -"exa-ampère", -"exa-ampères", -"exa-électron-volt", -"exaélectron-volt", -"exa-électron-volts", -"exaélectron-volts", -"ex-aequo", -"ex-æquo", -"ex-ante", -"exa-octet", -"exa-octets", -"ex-champions", -"excito-nervin", -"excito-nervine", -"excito-nervines", -"excito-nervins", -"ex-copains", -"excusez-moi", -"ex-député", -"ex-députée", -"ex-députées", -"ex-députés", -"ex-femme", -"ex-femmes", -"ex-fumeur", -"ex-fumeurs", -"ex-libris", -"ex-mari", -"ex-maris", -"exo-noyau", -"exo-noyaux", -"expert-comptable", -"ex-petits", -"ex-présidents", -"ex-sacs", -"ex-sergents", -"ex-serviteurs", -"ex-soldats", -"ex-strip-teaseuse", -"extracto-chargeur", -"extracto-chargeurs", -"extracto-résine", -"extracto-résineux", -"extrêmes-droites", -"extrêmes-gauches", -"extrêmes-onctions", -"extro-déterminé", -"ex-voto", -"ex-votos", -"ex-Zaïre", -"eye-liner", -"eye-liners", "Eygluy-Escoulin", "Eygurande-et-Gardedeuil", "Eyres-Moncube", @@ -10223,98 +4483,24 @@ FR_BASE_EXCEPTIONS = [ "Eyzin-Pinet", "Ezkio-Itsaso", "Ezy-sur-Eure", -"Ézy-sur-Eure", -"face-à-face", -"face-à-main", -"face-B", -"face-kini", -"face-kinis", -"faces-à-main", -"faces-B", -"face-sitting", -"face-sittings", +"Eßleben-Teutleben", "Faches-Thumesnil", -"faches-thumesnilois", "Faches-Thumesnilois", -"faches-thumesniloise", "Faches-Thumesniloise", -"faches-thumesniloises", "Faches-Thumesniloises", -"fac-simila", -"fac-similai", -"fac-similaient", -"fac-similaire", -"fac-similais", -"fac-similait", -"fac-similâmes", -"fac-similant", -"fac-similas", -"fac-similasse", -"fac-similassent", -"fac-similasses", -"fac-similassiez", -"fac-similassions", -"fac-similât", -"fac-similâtes", -"fac-simile", -"fac-similé", -"fac-similée", -"fac-similées", -"fac-similent", -"fac-similer", -"fac-similera", -"fac-similerai", -"fac-simileraient", -"fac-similerais", -"fac-similerait", -"fac-simileras", -"fac-similèrent", -"fac-similerez", -"fac-simileriez", -"fac-similerions", -"fac-similerons", -"fac-simileront", -"fac-similes", -"fac-similés", -"fac-similez", -"fac-similiez", -"fac-similions", -"fac-similons", "Faget-Abbatial", "Fahy-lès-Autrey", -"faim-valle", "Fain-lès-Montbard", "Fain-lès-Moutiers", -"Fains-la-Folie", "Fains-Véel", -"faire-part", -"faire-savoir", -"faire-valoir", -"fair-play", -"fair-plays", -"fait-à-fait", -"fait-divers", -"fait-diversier", -"fait-diversiers", -"fait-main", -"faits-divers", -"faits-diversier", -"faits-diversiers", -"fait-tout", +"Fains-la-Folie", "Fajac-en-Val", "Fajac-la-Relenque", "Falkenberg-sur-Elster", -"fan-club", -"fan-clubs", -"fancy-fair", -"fancy-fairs", -"farcy-pontain", +"Far-West", "Farcy-Pontain", -"farcy-pontaine", "Farcy-Pontaine", -"farcy-pontaines", "Farcy-Pontaines", -"farcy-pontains", "Farcy-Pontains", "Fargau-Pratjau", "Farges-Allichamps", @@ -10323,341 +4509,134 @@ FR_BASE_EXCEPTIONS = [ "Farges-lès-Mâcon", "Fargues-Saint-Hilaire", "Fargues-sur-Ourbise", -"Far-West", -"fast-food", -"fast-foods", "Fatouville-Grestain", "Fatu-Hiva", +"Fau-de-Peyre", "Faucogney-et-la-Mer", "Faucon-de-Barcelonnette", "Faucon-du-Caire", -"Fau-de-Peyre", "Faulx-les-Tombes", "Fauquemont-sur-Gueule", -"fausse-braie", -"fausse-couche", -"fausse-limande", -"fausse-monnayeuse", -"fausse-porte", -"fausses-braies", -"fausses-couches", -"fausses-monnayeuses", "Fauville-en-Caux", -"faux-acacia", -"faux-acacias", -"faux-ami", -"faux-amis", -"faux-bourdon", -"faux-bourdons", -"faux-bras", -"faux-carré", -"faux-carrés", -"faux-champlevé", -"faux-col", -"faux-cols", -"faux-cul", -"faux-derche", -"faux-derches", -"faux-filet", -"faux-filets", -"faux-frais", -"faux-frère", -"faux-frères", "Faux-Fresnay", -"faux-fruit", -"faux-fruits", -"faux-fuyans", -"faux-fuyant", -"faux-fuyants", -"faux-garou", -"faux-grenier", -"faux-greniers", -"faux-jeton", -"faux-jetons", -"Faux-la-Montagne", "Faux-Mazuras", -"faux-monnayage", -"faux-monnayages", -"faux-monnayeur", -"faux-monnayeurs", -"faux-nez", -"faux-palais", -"faux-persil", -"faux-poivrier", -"faux-poivriers", -"faux-pont", -"faux-ponts", -"faux-positif", -"faux-positifs", -"faux-saunage", -"faux-saunier", -"faux-saunière", -"faux-saunières", -"faux-sauniers", -"faux-scaphirhynque", -"faux-semblans", -"faux-semblant", -"faux-semblants", -"faux-sens", -"faux-vampire", -"faux-vampires", -"Faux-Vésigneul", "Faux-Villecerf", -"faux-vin", +"Faux-Vésigneul", +"Faux-la-Montagne", "Faveraye-Mâchelles", -"Faverges-de-la-Tour", "Faverges-Seythenex", +"Faverges-de-la-Tour", "Faverolles-et-Coëmy", "Faverolles-la-Campagne", -"Faverolles-lès-Lucey", "Faverolles-les-Mares", +"Faverolles-lès-Lucey", "Faverolles-sur-Cher", -"fax-tractage", -"fax-tractages", "Fay-aux-Loges", "Fay-de-Bretagne", -"Faye-d'Anjou", -"Faye-l'Abbesse", -"Faye-la-Vineuse", "Fay-en-Montagne", -"Faye-sur-Ardin", -"Fayet-le-Château", -"Fayet-Ronaye", -"Fayl-Billot", -"fayl-billotin", -"Fayl-Billotin", -"fayl-billotine", -"Fayl-Billotine", -"fayl-billotines", -"Fayl-Billotines", -"fayl-billotins", -"Fayl-Billotins", "Fay-le-Clos", "Fay-les-Etangs", "Fay-les-Étangs", "Fay-lès-Marcilly", -"Faÿ-lès-Nemours", +"Fay-sur-Lignon", +"Faye-d'Anjou", +"Faye-l'Abbesse", +"Faye-la-Vineuse", +"Faye-sur-Ardin", +"Fayet-Ronaye", +"Fayet-le-Château", +"Fayl-Billot", +"Fayl-Billotin", +"Fayl-Billotine", +"Fayl-Billotines", +"Fayl-Billotins", "Fayl-la-Forêt", "Fays-la-Chapelle", "Fays-les-Veneurs", -"Fay-sur-Lignon", "Fayt-le-Franc", "Fayt-lez-Manage", +"Faÿ-lès-Nemours", "Febvin-Palfart", -"Fêche-l'Eglise", -"Fêche-l'Église", -"fech-fech", -"feed-back", "Fehl-Ritzhausen", "Feins-en-Gâtinais", "Feissons-sur-Isère", "Feissons-sur-Salins", "Felben-Wellhausen", "Feldkirchen-Westerham", -"Félines-Minervois", -"Félines-sur-Rimandoule", -"Félines-Termenès", -"femelle-stérile", -"femelle-stériles", -"femme-enfant", -"femme-objet", -"femme-orchestre", -"femme-renarde", -"femmes-enfants", -"femmes-orchestres", -"femmes-renardes", -"fémoro-tibial", -"femto-ohm", -"femto-ohms", "Fenouillet-du-Razès", -"fénoxaprop-éthyl", -"fénoxaprop-P-éthyl", -"féodo-vassalique", -"féodo-vassaliques", -"fer-à-cheval", -"fer-blanc", "Fercé-sur-Sarthe", -"fer-chaud", -"fer-de-lance", -"fer-de-moulin", -"Fère-Champenoise", -"Fère-en-Tardenois", -"ferme-bourse", -"ferme-circuit", -"ferme-circuits", "Ferme-Neuvien", -"ferme-porte", -"ferme-portes", -"fermes-hôtels", -"fermier-général", -"Fernán-Núñez", "Ferney-Voltaire", -"Férolles-Attilly", +"Fernán-Núñez", "Ferrals-les-Corbières", "Ferrals-les-Montagnes", -"ferrando-forézienne", -"ferre-mule", "Ferreux-Quincey", +"Ferrière-Larçon", "Ferrière-et-Lafolie", "Ferrière-la-Grande", "Ferrière-la-Petite", -"Ferrière-Larçon", -"Ferrières-en-Bray", -"Ferrières-en-Brie", -"Ferrières-en-Gâtinais", +"Ferrière-sur-Beaulieu", "Ferrières-Haut-Clocher", -"Ferrières-la-Verrerie", -"Ferrières-le-Lac", -"Ferrières-les-Bois", -"Ferrières-lès-Ray", -"Ferrières-lès-Scey", -"Ferrières-les-Verreries", "Ferrières-Poussarou", "Ferrières-Saint-Hilaire", "Ferrières-Saint-Mary", +"Ferrières-en-Bray", +"Ferrières-en-Brie", +"Ferrières-en-Gâtinais", +"Ferrières-la-Verrerie", +"Ferrières-le-Lac", +"Ferrières-les-Bois", +"Ferrières-les-Verreries", +"Ferrières-lès-Ray", +"Ferrières-lès-Scey", "Ferrières-sur-Ariège", "Ferrières-sur-Sichon", -"Ferrière-sur-Beaulieu", -"ferro-axinite", -"ferro-axinites", -"ferro-magnésien", -"ferro-magnétisme", -"ferro-magnétismes", -"ferro-phlogopite", -"ferro-phlogopites", -"ferro-prussiate", -"ferro-prussiates", -"ferry-boat", -"ferry-boats", -"fers-à-cheval", -"fers-blancs", -"fers-de-lance", "Fesches-le-Châtel", -"fesh-fesh", "Fesmy-le-Sart", "Fessanvilliers-Mattanvilliers", -"fesse-cahier", -"fesse-mathieu", -"fesse-mathieus", -"fesse-mathieux", "Fessenheim-le-Bas", -"fesse-tonneau", -"fesse-tonneaux", "Fessey-Dessous-et-Dessus", -"fest-deiz", "Festes-et-Saint-André", -"fest-noz", -"fest-nozs", -"Fête-Dieu", -"fétu-en-cul", -"fétus-en-cul", "Feuguerolles-Bully", "Feuguerolles-sur-Orne", "Feuguerolles-sur-Seulles", -"feuille-caillou-ciseaux", -"feuille-morte", "Feuquières-en-Vimeu", -"Fexhe-le-Haut-Clocher", "Fexhe-Slins", +"Fexhe-le-Haut-Clocher", "Fey-en-Haye", -"fibre-cellule", -"fibro-cartilage", -"fibro-cellulaire", -"fibro-cystique", -"fibro-cystiques", -"fibro-granulaire", -"fibro-muqueux", -"fibro-séreux", -"fibro-soyeux", -"fiche-échalas", "Fichous-Riumayou", -"fiducie-sûreté", "Fieffes-Montrelet", -"fier-à-bras", -"fiers-à-bras", "Fierville-Bray", "Fierville-les-Mines", "Fierville-les-Parcs", -"fie-vïnnamide", -"fie-vïnnamides", -"fifty-fifty", "Figaró-Montmany", -"figuier-mûrier", -"filet-poubelle", -"filets-poubelles", -"fille-mère", -"filles-mères", -"film-fleuve", -"films-annonces", -"fils-de-puterie", -"filtre-presse", -"filtres-presses", -"fine-metal", "Finkenbach-Gersweiler", -"finno-ougrien", -"finno-ougrienne", -"finno-ougriennes", -"finno-ougriens", -"fin-or", "Fiquefleur-Equainville", "Fiquefleur-Équainville", -"first-fit", "Fischbach-Göslikon", "Fischbach-Oberraden", -"fisse-larron", -"fisses-larrons", -"fist-fucking", -"fist-fuckings", "Fitz-James", -"fitz-jamois", "Fitz-Jamois", -"fitz-jamoise", "Fitz-Jamoise", -"fitz-jamoises", "Fitz-Jamoises", -"fixe-chaussette", -"fixe-chaussettes", -"fixe-fruit", -"fixe-fruits", -"fixe-longe", -"fixe-moustaches", -"fixe-ruban", -"fixe-rubans", "Fix-Saint-Geneys", -"fix-up", "Fize-Fontaine", "Fize-le-Marsal", -"f'jer", -"f'jers", -"Flacé-lès-Mâcon", "Flacey-en-Bresse", -"fla-fla", -"fla-flas", +"Flacé-lès-Mâcon", "Flagey-Echézeaux", -"Flagey-Échézeaux", -"Flagey-lès-Auxonne", "Flagey-Rigney", +"Flagey-lès-Auxonne", +"Flagey-Échézeaux", "Flaignes-Havys", "Flaignes-les-Oliviers", "Flamets-Frétils", -"flanc-de-chien", "Flanc-de-chien", -"flanc-garde", -"flanc-gardes", -"flanc-mou", "Flancourt-Catelon", "Flancourt-Crescy-en-Roumois", -"flancs-de-chien", "Flancs-de-chien", -"flancs-gardes", -"flancs-mous", "Flandre-Occidentale", "Flandre-Orientale", -"flash-back", -"flash-ball", -"flash-balls", -"flash-mob", -"flash-mobs", "Flassans-sur-Issole", "Flaujac-Gare", "Flaujac-Poujols", @@ -10668,60 +4647,18 @@ FR_BASE_EXCEPTIONS = [ "Flavigny-sur-Ozerain", "Flavy-le-Martel", "Flavy-le-Meldeux", -"Fléac-sur-Seugne", -"Flémalle-Grande", -"Flémalle-Haute", -"Fléré-la-Rivière", "Flers-en-Escrebieux", "Flers-lez-Lille", "Flers-sur-Noye", -"fleur-bleuisa", -"fleur-bleuisai", -"fleur-bleuisaient", -"fleur-bleuisais", -"fleur-bleuisait", -"fleur-bleuisâmes", -"fleur-bleuisant", -"fleur-bleuisas", -"fleur-bleuisasse", -"fleur-bleuisassent", -"fleur-bleuisasses", -"fleur-bleuisassiez", -"fleur-bleuisassions", -"fleur-bleuisât", -"fleur-bleuisâtes", -"fleur-bleuise", -"fleur-bleuisé", -"fleur-bleuisée", -"fleur-bleuisées", -"fleur-bleuisent", -"fleur-bleuiser", -"fleur-bleuisera", -"fleur-bleuiserai", -"fleur-bleuiseraient", -"fleur-bleuiserais", -"fleur-bleuiserait", -"fleur-bleuiseras", -"fleur-bleuisèrent", -"fleur-bleuiserez", -"fleur-bleuiseriez", -"fleur-bleuiserions", -"fleur-bleuiserons", -"fleur-bleuiseront", -"fleur-bleuises", -"fleur-bleuisés", -"fleur-bleuisez", -"fleur-bleuisiez", -"fleur-bleuisions", -"fleur-bleuisons", -"fleur-de-mai", "Fleurey-lès-Faverney", "Fleurey-lès-Lavoncourt", "Fleurey-lès-Saint-Loup", "Fleurey-sur-Ouche", -"fleur-feuille", "Fleurieu-sur-Saône", "Fleurieux-sur-l'Arbresle", +"Fleury-Montmarin", +"Fleury-Mérogis", +"Fleury-Vallée-d'Aillant", "Fleury-devant-Douaumont", "Fleury-en-Bière", "Fleury-et-Montmarin", @@ -10730,251 +4667,140 @@ FR_BASE_EXCEPTIONS = [ "Fleury-la-Rivière", "Fleury-la-Vallée", "Fleury-les-Aubrais", -"Fleury-Mérogis", -"Fleury-Montmarin", "Fleury-sur-Aire", "Fleury-sur-Andelle", "Fleury-sur-Loire", "Fleury-sur-Orne", -"Fleury-Vallée-d'Aillant", -"Fléville-devant-Nancy", -"Fléville-Lixières", "Flez-Cuzy", -"flic-flac", -"flic-flaqua", -"flic-flaquai", -"flic-flaquaient", -"flic-flaquais", -"flic-flaquait", -"flic-flaquâmes", -"flic-flaquant", -"flic-flaquas", -"flic-flaquasse", -"flic-flaquassent", -"flic-flaquasses", -"flic-flaquassiez", -"flic-flaquassions", -"flic-flaquât", -"flic-flaquâtes", -"flic-flaque", -"flic-flaqué", -"flic-flaquent", -"flic-flaquer", -"flic-flaquera", -"flic-flaquerai", -"flic-flaqueraient", -"flic-flaquerais", -"flic-flaquerait", -"flic-flaqueras", -"flic-flaquèrent", -"flic-flaquerez", -"flic-flaqueriez", -"flic-flaquerions", -"flic-flaquerons", -"flic-flaqueront", -"flic-flaques", -"flic-flaquez", -"flic-flaquiez", -"flic-flaquions", -"flic-flaquons", "Flieth-Stegelitz", -"Flines-lès-Mortagne", "Flines-lez-Raches", +"Flines-lès-Mortagne", "Flins-Neuve-Eglise", "Flins-Neuve-Église", "Flins-sur-Seine", -"flint-glass", -"flip-flap", -"flirty-fishing", -"float-tube", -"float-tubes", "Flogny-la-Chapelle", "Floh-Seligenthal", "Florent-en-Argonne", "Florentin-la-Capelle", "Florimont-Gaumier", -"Flörsheim-Dalsheim", -"flos-ferré", -"flos-ferri", "Flottemanville-Hague", -"flotte-tube", -"flotte-tubes", -"flou-flou", -"fluazifop-butyl", -"fluazifop-P-butyl", "Fluorn-Winzeln", -"fluoro-phlogopite", -"fluoro-phlogopites", -"flupyrsulfuron-méthyle", -"fluroxypyr-meptyl", -"fluvio-marin", -"fly-over", -"fly-overs", -"fly-tox", -"f'nêtre", -"f'nêtres", +"Fléac-sur-Seugne", +"Flémalle-Grande", +"Flémalle-Haute", +"Fléré-la-Rivière", +"Fléville-Lixières", +"Fléville-devant-Nancy", +"Flörsheim-Dalsheim", "Foameix-Ornel", -"foc-en-l'air", -"Föhrden-Barl", "Fohren-Linden", -"foie-de-boeuf", -"foies-de-boeuf", -"foi-menti", -"foi-mentie", -"foire-exposition", -"foires-expositions", "Foissy-lès-Vézelay", "Foissy-sur-Vanne", -"folk-lore", -"folk-lores", "Follainville-Dennemont", -"folle-avoine", -"folle-blanche", -"folles-avoines", -"folle-verte", "Folx-les-Caves", -"folx-les-cavien", "Folx-les-Cavien", "Folx-les-Cavienne", "Fonches-Fonchette", "Foncine-le-Bas", "Foncine-le-Haut", "Fondachelli-Fantina", -"fond-de-teinta", -"fond-de-teintai", -"fond-de-teintaient", -"fond-de-teintais", -"fond-de-teintait", -"fond-de-teintâmes", -"fond-de-teintant", -"fond-de-teintas", -"fond-de-teintasse", -"fond-de-teintassent", -"fond-de-teintasses", -"fond-de-teintassiez", -"fond-de-teintassions", -"fond-de-teintât", -"fond-de-teintâtes", -"fond-de-teinte", -"fond-de-teinté", -"fond-de-teintée", -"fond-de-teintées", -"fond-de-teintent", -"fond-de-teinter", -"fond-de-teintera", -"fond-de-teinterai", -"fond-de-teinteraient", -"fond-de-teinterais", -"fond-de-teinterait", -"fond-de-teinteras", -"fond-de-teintèrent", -"fond-de-teinterez", -"fond-de-teinteriez", -"fond-de-teinterions", -"fond-de-teinterons", -"fond-de-teinteront", -"fond-de-teintes", -"fond-de-teintés", -"fond-de-teintez", -"fond-de-teintiez", -"fond-de-teintions", -"fond-de-teintons", "Fonds-Saint-Denis", -"fon-gbe", "Fons-sur-Lussan", -"Fontaine-au-Bois", -"Fontaine-au-Pire", +"Font-Romeu-Odeillo-Via", +"Font-de-Carpentin", +"Font-rubí", "Fontaine-Bellenger", "Fontaine-Bethon", "Fontaine-Bonneleau", -"fontaine-brayen", "Fontaine-Brayen", -"fontaine-brayenne", "Fontaine-Brayenne", -"fontaine-brayennes", "Fontaine-Brayennes", -"fontaine-brayens", "Fontaine-Brayens", "Fontaine-Chaalis", "Fontaine-Chalendray", "Fontaine-Couverte", "Fontaine-Denis", "Fontaine-Denis-Nuisy", -"Fontaine-de-Vaucluse", -"Fontaine-en-Bray", -"Fontaine-en-Dormois", "Fontaine-Etoupefour", -"Fontaine-Étoupefour", "Fontaine-Fourches", "Fontaine-Française", "Fontaine-Guérin", "Fontaine-Henry", "Fontaine-Heudebourg", +"Fontaine-Lavaganne", +"Fontaine-Luyères", +"Fontaine-Milon", +"Fontaine-Mâcon", +"Fontaine-Notre-Dame", +"Fontaine-Raoul", +"Fontaine-Saint-Lucien", +"Fontaine-Simon", +"Fontaine-Uterte", +"Fontaine-Valmont", +"Fontaine-au-Bois", +"Fontaine-au-Pire", +"Fontaine-de-Vaucluse", +"Fontaine-en-Bray", +"Fontaine-en-Dormois", "Fontaine-l'Abbé", +"Fontaine-l'Etalon", +"Fontaine-l'Étalon", +"Fontaine-l'Évêque", "Fontaine-la-Gaillarde", "Fontaine-la-Guyon", "Fontaine-la-Louvet", "Fontaine-la-Mallet", "Fontaine-la-Rivière", "Fontaine-la-Soret", -"Fontaine-Lavaganne", "Fontaine-le-Bourg", "Fontaine-le-Comte", "Fontaine-le-Dun", "Fontaine-le-Pin", "Fontaine-le-Port", "Fontaine-le-Puits", +"Fontaine-le-Sec", "Fontaine-les-Bassets", +"Fontaine-les-Coteaux", +"Fontaine-les-Grès", +"Fontaine-les-Ribouts", "Fontaine-lès-Boulans", "Fontaine-lès-Cappy", "Fontaine-lès-Clercs", "Fontaine-lès-Clerval", -"Fontaine-les-Coteaux", "Fontaine-lès-Croisilles", "Fontaine-lès-Dijon", -"Fontaine-le-Sec", -"Fontaine-les-Grès", "Fontaine-lès-Hermans", "Fontaine-lès-Luxeuil", -"Fontaine-les-Ribouts", "Fontaine-lès-Vervins", -"Fontaine-l'Etalon", -"Fontaine-l'Étalon", -"Fontaine-l'Évêque", -"Fontaine-Luyères", -"Fontaine-Mâcon", -"Fontaine-Milon", -"Fontaine-Notre-Dame", -"Fontaine-Raoul", -"Fontaine-Saint-Lucien", -"Fontaines-d'Ozillac", -"Fontaines-en-Duesmois", -"Fontaines-en-Sologne", -"Fontaine-Simon", -"Fontaines-les-Sèches", "Fontaine-sous-Jouy", "Fontaine-sous-Montaiguillon", "Fontaine-sous-Montdidier", "Fontaine-sous-Pezou", "Fontaine-sous-Préaux", -"Fontaines-Saint-Clair", -"Fontaines-Saint-Martin", -"Fontaines-sur-Grandson", -"Fontaines-sur-Marne", -"Fontaines-sur-Saône", "Fontaine-sur-Ay", "Fontaine-sur-Coole", "Fontaine-sur-Maye", "Fontaine-sur-Somme", -"Fontaine-Uterte", -"Fontaine-Valmont", -"Fontanès-de-Sault", +"Fontaine-Étoupefour", +"Fontaines-Saint-Clair", +"Fontaines-Saint-Martin", +"Fontaines-d'Ozillac", +"Fontaines-en-Duesmois", +"Fontaines-en-Sologne", +"Fontaines-les-Sèches", +"Fontaines-sur-Grandson", +"Fontaines-sur-Marne", +"Fontaines-sur-Saône", "Fontanes-du-Causse", "Fontanil-Cornillon", +"Fontanès-de-Sault", "Fontcouverte-la-Toussuire", -"Font-de-Carpentin", "Fontenai-les-Louvets", "Fontenai-sur-Orne", +"Fontenay-Mauvoisin", +"Fontenay-Saint-Père", +"Fontenay-Torcy", +"Fontenay-Trésigny", "Fontenay-aux-Roses", "Fontenay-de-Bossery", "Fontenay-en-Parisis", @@ -10983,12 +4809,10 @@ FR_BASE_EXCEPTIONS = [ "Fontenay-le-Fleury", "Fontenay-le-Marmion", "Fontenay-le-Pesnel", -"Fontenay-lès-Briis", "Fontenay-le-Vicomte", -"Fontenay-Mauvoisin", +"Fontenay-lès-Briis", "Fontenay-près-Chablis", "Fontenay-près-Vézelay", -"Fontenay-Saint-Père", "Fontenay-sous-Bois", "Fontenay-sous-Fouronnes", "Fontenay-sur-Conie", @@ -10996,10 +4820,8 @@ FR_BASE_EXCEPTIONS = [ "Fontenay-sur-Loing", "Fontenay-sur-Mer", "Fontenay-sur-Vègre", -"Fontenay-Torcy", -"Fontenay-Trésigny", -"Fontenelle-en-Brie", "Fontenelle-Montby", +"Fontenelle-en-Brie", "Fontenille-Saint-Martin-d'Entraigues", "Fontenilles-d'Aigueparse", "Fontenois-la-Ville", @@ -11012,175 +4834,86 @@ FR_BASE_EXCEPTIONS = [ "Fontevraud-l'Abbaye", "Fontiers-Cabardès", "Fontiès-d'Aude", -"Font-Romeu-Odeillo-Via", -"Font-rubí", -"food-court", -"food-courts", -"food-truck", -"food-trucks", "Forcelles-Saint-Gorgon", "Forcelles-sous-Gugney", "Forceville-en-Vimeu", -"force-vivier", "Forchies-la-Marche", "Forel-sur-Lucens", -"Forest-en-Cambrésis", -"Forest-l'Abbaye", "Forest-Montiers", "Forest-Saint-Julien", +"Forest-en-Cambrésis", +"Forest-l'Abbaye", "Forest-sur-Marque", -"forêt-clairière", -"forêt-climax", -"forêt-galerie", -"Forêt-la-Folie", -"Forêt-Noire", -"Forêt-Noire-Baar", -"forêt-parc", -"forêts-clairières", -"forêts-climax", -"forêts-galeries", -"forêts-parcs", -"forge-mètre", "Forge-Philippe", "Forges-la-Forêt", "Forges-les-Bains", "Forges-les-Eaux", "Forges-sur-Meuse", "Forlì-Cesena", -"formica-leo", -"formule-choc", -"formule-chocs", -"forsétyl-al", "Forst-Längenbühl", +"Fort-Louis", +"Fort-Mahon-Plage", +"Fort-Moville", +"Fort-de-France", +"Fort-du-Plasne", "Fortel-en-Artois", -"forte-piano", -"forte-pianos", -"forts-vêtu", -"Fosbury-flop", -"fosétyl-Al", -"Fossès-et-Baleyssac", -"Fosses-la-Ville", +"Forêt-Noire", +"Forêt-Noire-Baar", +"Forêt-la-Folie", "Fos-sur-Mer", -"Foucaucourt-en-Santerre", +"Fosbury-flop", +"Fosses-la-Ville", +"Fossès-et-Baleyssac", "Foucaucourt-Hors-Nesle", +"Foucaucourt-en-Santerre", "Foucaucourt-sur-Thabas", "Fouchères-aux-Bois", -"foué-toutrac", -"foué-toutracs", -"fouette-cul", -"fouette-culs", -"fouette-queue", -"fouette-queues", "Foufflin-Ricametz", "Foufnie-les-Berdouilles", "Fougax-et-Barrineuf", -"fougère-aigle", -"fougères-aigles", -"Fougères-sur-Bièvre", "Fougerolles-du-Plessis", -"fouille-au-pot", -"fouille-merde", -"foule-crapaud", +"Fougères-sur-Bièvre", "Fouquières-lès-Béthune", "Fouquières-lès-Lens", "Fourcatier-et-Maison-Neuve", -"fourche-fière", -"fourmi-lion", -"fourmis-lions", "Fourneaux-le-Val", "Fournes-Cabardès", "Fournes-en-Weppes", "Fournet-Blancheroche", "Fournets-Luisans", -"Fouron-le-Comte", "Fouron-Saint-Martin", "Fouron-Saint-Pierre", +"Fouron-le-Comte", "Fourques-sur-Garonne", -"fourre-tout", "Fours-en-Vexin", "Foussais-Payré", "Fouta-Diallon", "Fouta-Djalon", -"Fouvent-le-Bas", "Fouvent-Saint-Andoche", +"Fouvent-le-Bas", "Fox-Amphoux", -"fox-hound", -"fox-hounds", -"fox-terrier", -"fox-terriers", -"fox-trot", -"fox-trott", -"fox-trotta", -"fox-trottai", -"fox-trottaient", -"fox-trottais", -"fox-trottait", -"fox-trottâmes", -"fox-trottant", -"fox-trottas", -"fox-trottasse", -"fox-trottassent", -"fox-trottasses", -"fox-trottassiez", -"fox-trottassions", -"fox-trottât", -"fox-trottâtes", -"fox-trotte", -"fox-trotté", -"fox-trottent", -"fox-trotter", -"fox-trottera", -"fox-trotterai", -"fox-trotteraient", -"fox-trotterais", -"fox-trotterait", -"fox-trotteras", -"fox-trottèrent", -"fox-trotterez", -"fox-trotteriez", -"fox-trotterions", -"fox-trotterons", -"fox-trotteront", -"fox-trottes", -"fox-trottez", -"fox-trottiez", -"fox-trottions", -"fox-trottons", -"fox-trotts", "Foy-Notre-Dame", -"foy-notre-damien", "Foy-Notre-Damien", "Foy-Notre-Damienne", "Foz-Calanda", +"Fragnes-La Loyère", "Frahier-et-Chatebier", "Fraignot-et-Vesvrotte", -"frais-chier", "Fraisnes-en-Saintois", "Fraisse-Cabardès", -"Fraissé-des-Corbières", "Fraisse-sur-Agout", "Fraissinet-de-Fourques", "Fraissinet-de-Lozère", +"Fraissé-des-Corbières", "Framerville-Rainecourt", -"Francfort-sur-le-Main", "Francfort-sur-l'Oder", +"Francfort-sur-le-Main", "Franche-Comté", "Franches-Montagnes", "Francillon-sur-Roubion", "Francilly-Selency", "Frangy-en-Bresse", -"Fränkisch-Crumbach", "Franqueville-Saint-Pierre", -"frappe-abord", -"frappe-à-bord", -"frappe-à-mort", -"frappe-babord", -"frappe-d'abord", -"frappe-devant", -"frappe-main", -"frappe-mains", -"frappe-plaque", -"frappe-plaques", "Frasnay-Reugny", "Frasne-le-Château", "Frasne-les-Meulières", @@ -11189,86 +4922,71 @@ FR_BASE_EXCEPTIONS = [ "Frasnes-lez-Couvin", "Frasnes-lez-Gosselies", "Frayssinet-le-Gélat", -"Fréchet-Aure", -"Fréchou-Fréchet", -"Frédéric-Fontaine", "Fredersdorf-Vogelsdorf", -"free-lance", -"Freienstein-Teufen", "Frei-Laubersheim", -"freins-vapeur", -"frein-vapeur", +"Freienstein-Teufen", "Freix-Anglards", -"Frémeréville-sous-les-Côtes", "Frenelle-la-Grande", "Frenelle-la-Petite", "Freneuse-sur-Risle", "Fresnay-en-Retz", +"Fresnay-l'Evêque", +"Fresnay-l'Évêque", "Fresnay-le-Comte", "Fresnay-le-Gilmert", "Fresnay-le-Long", "Fresnay-le-Samson", -"Fresnay-l'Evêque", -"Fresnay-l'Évêque", "Fresnay-sur-Sarthe", -"Fresneaux-Montchevreuil", "Fresne-Cauverville", -"Fresné-la-Mère", -"Fresne-l'Archevêque", "Fresne-Léguillon", +"Fresne-Saint-Mamès", +"Fresne-l'Archevêque", "Fresne-le-Plan", "Fresne-lès-Reims", -"Fresne-Saint-Mamès", +"Fresneaux-Montchevreuil", +"Fresnes-Mazancourt", +"Fresnes-Tilloloy", "Fresnes-au-Mont", "Fresnes-en-Saulnois", "Fresnes-en-Tardenois", "Fresnes-en-Woëvre", "Fresnes-lès-Montauban", "Fresnes-lès-Reims", -"Fresnes-Mazancourt", "Fresnes-sur-Apance", "Fresnes-sur-Escaut", "Fresnes-sur-Marne", -"Fresnes-Tilloloy", "Fresney-le-Puceux", "Fresney-le-Vieux", "Fresnicourt-le-Dolmen", "Fresnois-la-Montagne", "Fresnoy-Andainville", +"Fresnoy-Folny", "Fresnoy-au-Val", "Fresnoy-en-Bassigny", "Fresnoy-en-Chaussée", "Fresnoy-en-Gohelle", "Fresnoy-en-Thelle", -"Fresnoy-Folny", "Fresnoy-la-Rivière", "Fresnoy-le-Château", "Fresnoy-le-Grand", "Fresnoy-le-Luat", "Fresnoy-lès-Roye", +"Fresné-la-Mère", "Fresse-sur-Moselle", "Fretigney-et-Velloreille", -"Frétoy-le-Château", -"Fréville-du-Gâtinais", -"Frévin-Capelle", "Freycenet-la-Cuche", "Freycenet-la-Tour", "Freyming-Merlebach", -"freyming-merlebachois", "Freyming-Merlebachois", -"freyming-merlebachoise", "Freyming-Merlebachoise", -"freyming-merlebachoises", "Freyming-Merlebachoises", "Freyung-Grafenau", "Fribourg-en-Brisgau", -"fric-frac", -"fric-fracs", "Friedrich-Wilhelm-Lübke-Koog", -"Frières-Faillouël", -"Frise-du-Nord", "Frise-Occidentale", +"Frise-du-Nord", "Friville-Escarbotin", +"Frières-Faillouël", "Frohen-le-Grand", "Frohen-le-Petit", "Frohen-sur-Authie", @@ -11276,100 +4994,58 @@ FR_BASE_EXCEPTIONS = [ "Fromeréville-les-Vallons", "Frontenay-Rohan-Rohan", "Frontenay-sur-Dive", -"Frontignan-de-Comminges", "Frontignan-Savès", -"fronto-iniaque", +"Frontignan-de-Comminges", "Frotey-lès-Lure", "Frotey-lès-Vesoul", -"frou-frou", -"frou-frous", -"frous-frous", "Frugerès-les-Mines", "Frugières-le-Pin", "Frutigen-Bas-Simmental", -"fuel-oil", -"fuel-oils", -"Fuente-Álamo", +"Fränkisch-Crumbach", +"Fréchet-Aure", +"Fréchou-Fréchet", +"Frédéric-Fontaine", +"Frémeréville-sous-les-Côtes", +"Frétoy-le-Château", +"Fréville-du-Gâtinais", +"Frévin-Capelle", "Fuente-Olmedo", "Fuente-Tójar", -"full-contact", +"Fuente-Álamo", "Full-Reuenthal", -"full-stack", -"fulmi-coton", -"fulmi-cotons", -"fume-cigare", -"fume-cigares", -"fume-cigarette", -"fume-cigarettes", -"fumée-gelée", -"fusée-sonde", -"fusilier-commando", -"fusilier-marin", -"fusiliers-commandos", -"fusiliers-marins", -"fusil-mitrailleur", -"fusils-mitrailleurs", -"fusion-acquisition", -"fute-fute", -"futes-futes", -"fût-et-fare", -"fut's", -"futuna-aniwa", +"Fère-Champenoise", +"Fère-en-Tardenois", +"Félines-Minervois", +"Félines-Termenès", +"Félines-sur-Rimandoule", +"Férolles-Attilly", +"Fêche-l'Eglise", +"Fêche-l'Église", +"Fête-Dieu", +"Föhrden-Barl", "Gaag-Maasland", "Gaag-Schipluiden", "Gaasterlân-Sleat", "Gabbioneta-Binanuova", -"gabrielino-fernandeño", -"gâche-métier", "Gadz'Arette", "Gadz'Arettes", -"gadz'arts", "Gadz'Arts", "Gageac-et-Rouillac", "Gagnac-sur-Cère", "Gagnac-sur-Garonne", -"gagnante-gagnante", -"gagnante-gagnante-gagnante", -"gagnantes-gagnantes", -"gagnantes-gagnantes-gagnantes", -"gagnant-gagnant", -"gagnant-gagnant-gagnant", -"gagnants-gagnants", -"gagnants-gagnants-gagnants", "Gagne-monopanglotte", -"gagne-pain", -"gagne-pains", -"gagne-petit", -"Gaillac-d'Aveyron", "Gaillac-Toulza", +"Gaillac-d'Aveyron", "Gaillan-en-Médoc", "Gaillardbois-Cressenville", -"gaillet-gratteron", -"gaillets-gratterons", "Gaillon-sur-Montcient", -"gaine-culotte", -"gaines-culottes", "Gaja-et-Villedieu", "Gaja-la-Selve", -"galaïco-portugais", -"galégo-portugais", -"galeries-refuges", -"galette-saucisse", -"galette-saucisses", "Gallargues-le-Montueux", "Gallin-Kuppentin", -"galvano-cautère", -"galvano-magnétique", -"galvano-magnétiques", -"galvano-magnétisme", -"galvano-magnétismes", "Gamaches-en-Vexin", "Gamarde-les-Bains", "Gamiz-Fika", -"gamma-1,2,3,4,5,6-hexachlorocyclohexane", -"gamma-HCH", -"gamma-hexachlorobenzène", -"gamma-hexachlorocyclohexane", "Gampel-Bratsch", "Gancourt-Saint-Etienne", "Gancourt-Saint-Étienne", @@ -11377,11 +5053,9 @@ FR_BASE_EXCEPTIONS = [ "Garancières-en-Beauce", "Garancières-en-Drouais", "Garcelles-Secqueville", -"garcette-goitre", +"Garde-Colombe", "Gardegan-et-Tourtirac", -"garden-parties", -"garden-party", -"garden-partys", +"Gardes-le-Pontaroux", "Garennes-sur-Eure", "Garges-lès-Gonesse", "Gargilesse-Dampierre", @@ -11390,155 +5064,83 @@ FR_BASE_EXCEPTIONS = [ "Garnat-sur-Engièvre", "Garrigues-Sainte-Eulalie", "Garzau-Garzin", -"gas-oil", -"gas-oils", "Gaspé-Nordien", "Gaspésie-Îles-de-la-Madeleine", "Gastines-sur-Erve", "Gasville-Oisème", -"gâte-bois", -"gâte-ménage", -"gâte-ménages", -"gâte-métier", -"gâte-métiers", -"gâte-papier", -"gâte-papiers", -"gâte-pâte", -"gâte-sauce", -"gâte-sauces", "Gatteville-le-Phare", "Gau-Algesheim", "Gau-Bickelheim", "Gau-Bischofsheim", -"gauche-fer", +"Gau-Heppenheim", +"Gau-Odernheim", +"Gau-Weinheim", "Gauchin-Légal", "Gauchin-Verloingt", "Gaudreville-la-Rivière", -"Gau-Heppenheim", -"Gau-Odernheim", "Gaurain-Ramecroix", "Gauville-la-Campagne", -"Gau-Weinheim", "Gavarnie-Gèdre", "Gavarret-sur-Aulouste", -"gay-friendly", -"gays-friendly", "Gazax-et-Baccarisse", -"gaz-cab", -"gazelle-girafe", -"gaz-poivre", -"Gée-Rivière", "Geest-Gérompont", "Geest-Gérompont-Petit-Rosière", -"Géfosse-Fontenay", -"gélatino-bromure", -"gélatino-bromures", -"gel-douche", -"gel-douches", "Geldrop-Mierlo", "Gelvécourt-et-Adompt", "Gemert-Bakel", "Genac-Bignac", -"Génicourt-sous-Condé", -"Génicourt-sur-Meuse", -"génie-conseil", -"génies-conseils", -"génio-hyoïdien", -"génio-hyoïdienne", -"génio-hyoïdiennes", -"génio-hyoïdiens", -"génito-crural", -"génito-urinaire", -"génito-urinaires", "Gennes-Ivergny", +"Gennes-Val de Loire", "Gennes-sur-Glaize", "Gennes-sur-Seiche", "Gensac-de-Boulogne", "Gensac-la-Pallue", "Gensac-sur-Garonne", "Gentioux-Pigerolles", -"gentleman-rider", -"gentlemen-riders", "Georges-Fontaine", "Gerbécourt-et-Haplemont", "Gercourt-et-Drillancourt", -"Gère-Bélesten", -"gère-bélestinois", -"Gère-Bélestinois", -"gère-bélestinoise", -"Gère-Bélestinoise", -"gère-bélestinoises", -"Gère-Bélestinoises", -"germanate-analcime", -"germanate-analcimes", -"germano-américain", -"germano-américaine", -"germano-américaines", -"germano-américains", -"germano-anglais", -"germano-anglaises", -"germano-iranien", "Germano-Iranien", -"germano-italo-japonais", +"Germigny-Pend-la-Pie", "Germigny-des-Prés", +"Germigny-l'Evêque", +"Germigny-l'Exempt", +"Germigny-l'Évêque", "Germigny-lès-Machault", "Germigny-lès-Machaut", -"Germigny-l'Evêque", -"Germigny-l'Évêque", -"Germigny-l'Exempt", -"Germigny-Pend-la-Pie", "Germigny-sous-Coulombs", "Germigny-sur-Loire", +"Germo-Roburien", +"Germo-Roburienne", +"Germo-Roburiennes", +"Germo-Roburiens", "Germolles-sur-Grosne", "Germond-Rouvre", -"germo-roburien", -"Germo-Roburien", -"germo-roburienne", -"Germo-Roburienne", -"germo-roburiennes", -"Germo-Roburiennes", -"germo-roburiens", -"Germo-Roburiens", "Germs-sur-l'Oussouet", "Gernika-Lumo", "Gerville-la-Forêt", "Gesnes-en-Argonne", "Gesnes-le-Gandelin", -"gestalt-thérapie", -"gestalt-thérapies", "Gesvres-le-Chapitre", -"gétah-lahoë", -"Géus-d'Arzacq", -"Geüs-d'Oloron", "Gevigney-et-Mercey", "Gevrey-Chambertin", "Gez-ez-Angles", "Gezier-et-Fontelenay", -"Gézier-et-Fontenelay", +"Geüs-d'Oloron", "Giardini-Naxos", "Giel-Courteilles", "Gien-sur-Cure", "Giessen-Nieuwkerk", "Giessen-Oudekerk", "Giey-sur-Aujon", -"Giffaumont-Champaubert", "Gif-sur-Yvette", -"giga-ampère", -"giga-ampères", -"gigabit-ethernet", -"giga-électron-volt", -"gigaélectron-volt", -"giga-électron-volts", -"gigaélectron-volts", -"giga-ohm", -"giga-ohms", +"Giffaumont-Champaubert", "Gignac-la-Nerthe", "Gigny-Bussy", "Gigny-sur-Saône", "Gigors-et-Lozeron", "Gilhac-et-Bruzac", "Gilhoc-sur-Ormèze", -"gill-box", "Gilly-lès-Cîteaux", "Gilly-sur-Isère", "Gilly-sur-Loire", @@ -11570,77 +5172,32 @@ FR_BASE_EXCEPTIONS = [ "Givry-lès-Loisy", "Givry-sur-Aisne", "Glabbeek-Zuurbemde", -"glabello-iniaque", "Glaine-Montaigut", -"Glaire-et-Villette", "Glaire-Latour", -"Glane-Beekhoek", +"Glaire-et-Villette", "Glan-Münchweiler", -"glass-cord", -"glauco-ferrugineuse", -"glauco-ferrugineuses", -"glauco-ferrugineux", +"Glane-Beekhoek", "Glaude-Arbourois", "Gleiszellen-Gleishorbach", -"glisser-déposer", -"globe-trotter", -"globe-trotters", -"globe-trotteur", -"globe-trotteurs", -"globe-trotteuse", -"globe-trotteuses", "Glos-la-Ferrière", -"glosso-épiglottique", -"glosso-épiglottiques", -"glosso-pharyngien", -"glosso-staphylin", -"glosso-staphylins", "Glos-sur-Risle", -"gloubi-boulga", -"gluco-corticoïde", -"gluco-corticoïdes", -"glufosinate-ammonium", "Glux-en-Glenne", -"glycéraldéhyde-3-phosphate", -"glycosyl-phosphatidylinositol", -"goal-average", -"goal-averages", -"goal-ball", -"gobe-dieu", -"gobe-goujons", -"gobe-mouche", -"gobe-moucherie", -"gobe-moucherons", -"gobe-mouches", -"gobe-mouton", -"gode-ceinture", -"gode-miché", -"gode-michés", -"godes-ceintures", -"Gœgnies-Chaussée", "Goeree-Overflakkee", "Gognies-Chaussée", -"Göhren-Döhlen", -"Göhren-Lebbin", "Goldbach-Altenbach", -"goma-dare", "Gometz-la-Ville", "Gometz-le-Châtel", -"gomme-cogne", -"gomme-cognes", -"gomme-gutte", "Gommenec'h", -"gomme-résine", -"gommo-résineux", "Gomzé-Andoumont", -"Gondenans-les-Moulins", +"Gond-Pontouvre", "Gondenans-Montby", "Gondenans-Moulins", -"Gond-Pontouvre", +"Gondenans-les-Moulins", "Gondrecourt-Aix", "Gondrecourt-le-Château", "Gonfreville-Caillot", "Gonfreville-l'Orcher", +"Gonneville-Le Theil", "Gonneville-en-Auge", "Gonneville-la-Mallet", "Gonneville-sur-Honfleur", @@ -11648,54 +5205,11 @@ FR_BASE_EXCEPTIONS = [ "Gonneville-sur-Merville", "Gonneville-sur-Scie", "Gontaud-de-Nogaret", -"google-isa", -"google-isai", -"google-isaient", -"google-isais", -"google-isait", -"google-isâmes", -"google-isant", -"google-isas", -"google-isasse", -"google-isassent", -"google-isasses", -"google-isassiez", -"google-isassions", -"google-isât", -"google-isâtes", -"google-ise", -"google-isé", -"google-isée", -"google-isées", -"google-isent", -"google-iser", -"google-isera", -"google-iserai", -"google-iseraient", -"google-iserais", -"google-iserait", -"google-iseras", -"google-isèrent", -"google-iserez", -"google-iseriez", -"google-iserions", -"google-iserons", -"google-iseront", -"google-ises", -"google-isés", -"google-isez", -"google-isiez", -"google-isions", -"google-isons", "Gorden-Staupitz", -"gorge-bleue", -"gorge-de-pigeon", -"gorge-fouille", "Gorges-du-Tarn-Causses", "Gornate-Olona", "Gorom-Gorom", "Gors-Opleeuw", -"go-slow", "Gossersweiler-Stein", "Gotein-Libarrenx", "Gouaux-de-Larboust", @@ -11704,242 +5218,151 @@ FR_BASE_EXCEPTIONS = [ "Goudelancourt-lès-Pierrepont", "Goulier-et-Olbier", "Gourdan-Polignan", -"gourdan-polignanais", "Gourdan-Polignanais", -"gourdan-polignanaise", "Gourdan-Polignanaise", -"gourdan-polignanaises", "Gourdan-Polignanaises", "Gourdon-Murat", -"gouris-taitien", "Gouris-Taitien", -"gouris-taitienne", "Gouris-Taitienne", -"gouris-taitiennes", "Gouris-Taitiennes", -"gouris-taitiens", "Gouris-Taitiens", +"Gournay-Loizé", "Gournay-en-Bray", "Gournay-le-Guérin", -"Gournay-Loizé", "Gournay-sur-Aronde", "Gournay-sur-Marne", "Gout-Rossignol", -"goutte-à-goutte", -"goutte-de-sang", -"goutte-de-suif", -"goutte-rose", -"gouttes-de-sang", -"Goux-lès-Dambelin", "Goux-les-Usiers", +"Goux-lès-Dambelin", "Goux-sous-Landet", -"Gouy-en-Artois", -"Gouy-en-Ternois", -"Gouy-les-Groseillers", -"Gouy-lez-Piéton", -"Gouy-l'Hôpital", "Gouy-Saint-André", "Gouy-Servins", +"Gouy-en-Artois", +"Gouy-en-Ternois", +"Gouy-l'Hôpital", +"Gouy-les-Groseillers", +"Gouy-lez-Piéton", "Gouy-sous-Bellonne", -"gouzi-gouzi", -"gouzis-gouzis", -"goyave-ananas", -"goyaves-ananas", "Graal-Müritz", "Graben-Neudorf", "Grabow-Below", -"Grâce-Berleur", -"Grâce-Hollogne", -"Grâce-Uzel", -"gracieux-berluron", "Gracieux-Berluron", "Gracieux-Berluronne", -"grâcieux-hollognois", -"Grâcieux-Hollognois", -"Grâcieux-Hollognoise", "Graffigny-Chemin", "Graignes-Mesnil-Angot", "Graincourt-lès-Havrincourt", -"grain-d'orge", "Grainville-Langannerie", +"Grainville-Ymauville", "Grainville-la-Teinturière", "Grainville-sur-Odon", "Grainville-sur-Ry", -"Grainville-Ymauville", "Grancey-le-Château-Neuvelle", "Grancey-sur-Ource", +"Grand'Combe-Châteleu", +"Grand'Combe-des-Bois", +"Grand'Landais", +"Grand'Landaise", +"Grand'Landaises", +"Grand'Landes", +"Grand'Mèrois", +"Grand'Mérien", +"Grand'Mérois", +"Grand'Rivière", +"Grand'hamien", +"Grand'hamienne", +"Grand'hamiennes", +"Grand'hamiens", +"Grand'mérois", +"Grand-Auverné", +"Grand-Bourg", +"Grand-Brassac", +"Grand-Camp", +"Grand-Champ", +"Grand-Charmont", +"Grand-Corent", +"Grand-Couronne", +"Grand-Failly", +"Grand-Fayt", +"Grand-Fort-Philippe", +"Grand-Fougeray", +"Grand-Laviers", +"Grand-Rozoy", +"Grand-Rullecourt", +"Grand-Santi", +"Grand-Verly", "Grandcamp-Maisy", "Grandchamp-le-Château", "Grandchamps-des-Fontaines", -"grand'chose", -"Grand'Combe-Châteleu", -"Grand'Combe-des-Bois", -"grand'faim", +"Grande-Rivière", +"Grande-Synthe", "Grandfontaine-sur-Creuse", -"grand'garde", -"grand'gardes", -"grandgousier-pélican", -"grand'hamien", -"Grand'hamien", -"grand'hamienne", -"Grand'hamienne", -"grand'hamiennes", -"Grand'hamiennes", -"grand'hamiens", -"Grand'hamiens", -"grand'honte", -"grand'hontes", -"grand'landais", -"Grand'Landais", -"grand'landaise", -"Grand'Landaise", -"grand'landaises", -"Grand'Landaises", -"Grand'Landes", "Grandlup-et-Fay", -"grand'maman", -"grand'mamans", -"grand'maternité", -"grand'maternités", -"grand'mère", -"grand'mères", -"Grand'Mérien", -"Grand'mérois", -"Grand'Mérois", -"Grand'Mèrois", -"grand'messe", -"grand'messes", -"grand'paternité", -"grand'paternités", "Grandpuits-Bailly-Carrois", -"Grand'Rivière", "Grandrupt-de-Bains", -"grand'tante", -"grand'tantes", -"Grandvelle-et-le-Perrenot", "Grandvelle-et-Perrenot", +"Grandvelle-et-le-Perrenot", "Grandville-Gaudreville", "Grandvillers-aux-Bois", "Grange-de-Vaivre", "Grange-le-Bocage", "Granges-Aumontzey", +"Granges-Maillot", +"Granges-Narboz", +"Granges-Paccot", +"Granges-Sainte-Marie", "Granges-d'Ans", "Granges-de-Plombières", "Granges-de-Vienney", "Granges-la-Ville", "Granges-le-Bourg", "Granges-les-Beaumont", -"Granges-Maillot", -"Granges-Narboz", -"Granges-Paccot", -"Granges-Sainte-Marie", "Granges-sur-Aube", "Granges-sur-Baume", "Granges-sur-Lot", "Granges-sur-Vologne", -"grano-lamellaire", "Granzay-Gript", -"grap-fruit", -"grap-fruits", -"grapho-moteur", -"grappe-fruit", -"gras-double", -"gras-doubles", -"gras-fondu", "Grateloup-Saint-Gayrand", -"grattes-ciels", -"grave-cimens", -"grave-ciment", -"grave-ciments", "Graveron-Sémerville", -"graves-ciment", "Graves-Saint-Amant", -"gravi-kora", +"Gray-la-Ville", +"Gray-la-Villois", +"Gray-la-Villoise", +"Gray-la-Villoises", "Grayan-et-l'Hôpital", "Graye-et-Charnay", "Graye-sur-Mer", -"Gray-la-Ville", -"gray-la-villois", -"Gray-la-Villois", -"gray-la-villoise", -"Gray-la-Villoise", -"gray-la-villoises", -"Gray-la-Villoises", -"Grébault-Mesnil", "Grebs-Niendorf", -"Grèce-Centrale", -"Grèce-Occidentale", -"Gréez-sur-Roc", -"Grégy-sur-Yerre", "Gremersdorf-Buchholz", "Grenade-sur-Garonne", "Grenade-sur-l'Adour", -"grenadiers-voltigeurs", -"grenadier-voltigeur", "Grenand-lès-Sombernon", "Grenant-lès-Sombernon", "Greneville-en-Beauce", "Grenier-Montgon", -"grenouilles-taureaux", -"grenouille-taureau", "Grenville-sur-la-Rouge", "Grenzach-Wyhlen", -"Gréoux-les-Bains", -"Grésigny-Sainte-Reine", "Gresse-en-Vercors", "Gressoney-La-Trinité", "Gressoney-Saint-Jean", -"Grésy-sur-Aix", -"Grésy-sur-Isère", "Gretz-Armainvilliers", -"Gréville-Hague", "Grez-Doiceau", -"Grez-en-Bouère", -"Grézet-Cavagnan", -"Grézieu-la-Varenne", -"Grézieu-le-Marché", -"Grézieux-le-Fromental", "Grez-Neuville", -"grez-neuvillois", "Grez-Neuvillois", -"grez-neuvilloise", "Grez-Neuvilloise", -"grez-neuvilloises", "Grez-Neuvilloises", +"Grez-en-Bouère", "Grez-sur-Loing", -"griche-dents", "Griesbach-au-Val", "Griesbach-le-Bastberg", "Griesheim-près-Molsheim", "Griesheim-sur-Souffel", "Griesheim-sur-Souffle", -"gri-gri", -"gri-gris", -"gril-au-vent", -"grille-midi", -"grille-pain", -"grille-pains", "Grimaucourt-en-Woëvre", "Grimaucourt-près-Sampigny", "Grincourt-lès-Pas", "Grindorff-Bizing", -"grippe-argent", -"grippe-chair", -"grippe-fromage", -"grippe-fromages", -"grippe-minaud", -"grippe-minauds", -"grippe-sou", -"grippe-sous", -"grise-bonne", -"grises-bonnes", -"gris-farinier", -"gris-fariniers", -"gris-gris", -"gris-pendart", -"gris-pendarts", -"Grisy-les-Plâtres", "Grisy-Suisnes", +"Grisy-les-Plâtres", "Grisy-sur-Seine", "Grivy-Loisy", "Groot-Abeele", @@ -11949,82 +5372,64 @@ FR_BASE_EXCEPTIONS = [ "Groot-Loon", "Groot-Valkenisse", "Groot-Wetsinge", +"Gros-Chastang", +"Gros-Morne", +"Gros-Réderching", "Grosbois-en-Montagne", "Grosbois-lès-Tichey", -"Groslée-Saint-Benoît", "Grosley-sur-Risle", -"Groß-Bieberau", -"grosse-de-fonte", -"grosse-gorge", +"Groslée-Saint-Benoit", +"Groslée-Saint-Benoît", +"Gross-Gerau", "Grosse-Islois", "Grosseto-Prugna", -"Gross-Gerau", -"Groß-Gerau", -"grosso-modo", -"Groß-Rohrheim", -"Großtreben-Zwethau", -"Groß-Umstadt", -"Groß-Zimmern", "Grote-Brogel", "Grote-Spouwen", "Grouches-Luchuel", -"Gruchet-le-Valasse", +"Groß-Bieberau", +"Groß-Gerau", +"Groß-Rohrheim", +"Groß-Umstadt", +"Groß-Zimmern", +"Großtreben-Zwethau", "Gruchet-Saint-Siméon", +"Gruchet-le-Valasse", "Gruey-lès-Surance", "Grugé-l'Hôpital", "Grun-Bordas", -"Grünhain-Beierfeld", "Grunow-Dammendorf", -"g-strophanthine", -"guarasu'we", +"Grâce-Berleur", +"Grâce-Hollogne", +"Grâce-Uzel", +"Grâcieux-Hollognois", +"Grâcieux-Hollognoise", +"Grèce-Centrale", +"Grèce-Occidentale", +"Grébault-Mesnil", +"Gréez-sur-Roc", +"Grégy-sur-Yerre", +"Gréoux-les-Bains", +"Grésigny-Sainte-Reine", +"Grésy-sur-Aix", +"Grésy-sur-Isère", +"Gréville-Hague", +"Grézet-Cavagnan", +"Grézieu-la-Varenne", +"Grézieu-le-Marché", +"Grézieux-le-Fromental", +"Grünhain-Beierfeld", "Gudmont-Villiers", -"Guéblange-lès-Dieuze", -"Guéblange-lès-Sarralbe", -"gué-d'allérien", -"Gué-d'Allérien", -"gué-d'allérienne", -"Gué-d'Allérienne", -"gué-d'allériennes", -"Gué-d'Allériennes", -"gué-d'allériens", -"Gué-d'Allériens", -"Gué-d'Hossus", -"Guémené-Penfao", -"Guémené-sur-Scorff", -"guerre-éclair", "Guessling-Hémering", -"guet-apens", -"guet-à-pent", -"guet-appens", -"guets-apens", -"guette-chemin", -"gueule-bée", -"gueule-de-loup", -"gueules-de-loup", "Gueutteville-les-Grès", "Gueytes-et-Labastide", "Gugney-aux-Aulx", -"guide-âne", -"guide-ânes", -"guide-fil", -"guide-fils", -"guide-main", -"guigne-cul", -"guigne-culs", "Guigneville-sur-Essonne", "Guignicourt-sur-Vence", "Guiler-sur-Goyen", -"guilherandaise-grangeoise", -"Guilherandaise-Grangeoise", -"guilherandaises-grangeoises", -"Guilherandaises-Grangeoises", -"guilherandais-grangeois", -"Guilherandais-Grangeois", "Guilherand-Granges", -"guili-guili", -"guili-guilis", -"guillemet-apostrophe", -"guillemets-apostrophes", +"Guilherandais-Grangeois", +"Guilherandaise-Grangeoise", +"Guilherandaises-Grangeoises", "Guilligomarc'h", "Guillon-les-Bains", "Guinarthe-Parenties", @@ -12036,86 +5441,65 @@ FR_BASE_EXCEPTIONS = [ "Guipry-Messac", "Guiry-en-Vexin", "Guitalens-L'Albarède", -"guitare-harpe", -"guitare-violoncelle", -"guitare-violoncelles", "Guitera-les-Bains", -"guit-guit", "Gujan-Mestras", -"gulf-stream", -"gulf-streams", -"Gülitz-Reetz", "Gulpen-Wittem", -"Gülzow-Prüzen", "Gumbrechtshoffen-Oberbronn", -"Günthersleben-Wechmar", "Gurcy-le-Châtel", "Gurgy-la-Ville", "Gurgy-le-Château", -"gusathion-éthyl", -"gusathion-méthyl", "Gusow-Platkow", "Gutenzell-Hürbel", "Gutierre-Muñoz", -"gut-komm", -"gutta-percha", "Guttet-Feschel", -"gutturo-maxillaire", "Guyans-Durnes", "Guyans-Vennes", "Guyencourt-Saulcourt", "Guyencourt-sur-Noye", -"gwich'in", +"Gué-d'Allérien", +"Gué-d'Allérienne", +"Gué-d'Allériennes", +"Gué-d'Allériens", +"Gué-d'Hossus", +"Guéblange-lès-Dieuze", +"Guéblange-lès-Sarralbe", +"Guémené-Penfao", +"Guémené-sur-Scorff", "Gy-en-Sologne", -"Gyé-sur-Seine", -"Gy-les-Nonains", "Gy-l'Evêque", "Gy-l'Évêque", +"Gy-les-Nonains", +"Gyé-sur-Seine", +"Gère-Bélesten", +"Gère-Bélestinois", +"Gère-Bélestinoise", +"Gère-Bélestinoises", +"Gée-Rivière", +"Géfosse-Fontenay", +"Génicourt-sous-Condé", +"Génicourt-sur-Meuse", +"Géus-d'Arzacq", +"Gézier-et-Fontenelay", +"Göhren-Döhlen", +"Göhren-Lebbin", +"Gülitz-Reetz", +"Gülzow-Prüzen", +"Günthersleben-Wechmar", +"Gœgnies-Chaussée", "Ha'ava", "Habay-la-Neuve", "Habay-la-Vieille", "Habère-Lullin", "Habère-Poche", -"hache-bâché", -"hache-écorce", -"hache-écorces", -"hache-légume", -"hache-légumes", -"hache-paille", -"hache-pailles", "Hadancourt-le-Haut-Clocher", "Hadigny-les-Verrières", "Hadonville-lès-Lachaussée", -"Häg-Ehrsberg", "Hagenthal-le-Bas", "Hagenthal-le-Haut", -"hagio-onomastique", -"hagio-onomastiques", "Hagnéville-et-Roncourt", -"ha-ha", -"hâ-hâ", -"ha-has", -"hâ-hâs", "Haine-Saint-Paul", "Haine-Saint-Pierre", -"hakko-ryu", -"hale-à-bord", -"hale-avans", -"hale-avant", -"hale-avants", -"hale-bas", -"hale-breu", -"hale-croc", -"hale-dedans", -"hale-dehors", -"haleine-de-Jupiter", -"haleines-de-Jupiter", "Halenbeck-Rohlsdorf", -"half-and-half", -"half-pipe", -"half-pipes", -"half-track", -"half-tracks", "Halifaxois-du-Sud", "Halle-Booienhoven", "Halle-Heide", @@ -12125,181 +5509,114 @@ FR_BASE_EXCEPTIONS = [ "Halling-lès-Boulay", "Halling-lès-Boulay-Moselle", "Halloy-lès-Pernois", -"halo-halo", -"halo-lunaire", -"halos-lunaires", -"haloxyfop-éthoxyéthyl", -"haloxyfop-R", -"halte-garderie", -"halte-garderies", -"halte-là", -"haltes-garderies", -"halvadji-bachi", -"Hamblain-les-Prés", -"Hamelin-Pyrmont", -"Ham-en-Artois", -"Hames-Boucres", -"hames-boucrois", -"Hames-Boucrois", -"hames-boucroise", -"Hames-Boucroise", -"hames-boucroises", -"Hames-Boucroises", -"Ham-les-Moines", -"Hamme-Mille", -"hamme-millois", -"Hamme-Millois", -"Hamme-Milloise", -"ham-nalinnois", "Ham-Nalinnois", "Ham-Nalinnoise", "Ham-Nordois", -"Hamont-Achel", +"Ham-en-Artois", +"Ham-les-Moines", "Ham-sans-Culottes", "Ham-sous-Varsberg", "Ham-sur-Heure", "Ham-sur-Heure-Nalinnes", "Ham-sur-Meuse", "Ham-sur-Sambre", +"Hamblain-les-Prés", +"Hamelin-Pyrmont", +"Hames-Boucres", +"Hames-Boucrois", +"Hames-Boucroise", +"Hames-Boucroises", +"Hamme-Mille", +"Hamme-Millois", +"Hamme-Milloise", +"Hamont-Achel", "Han-devant-Pierrepont", -"handi-accessible", -"handi-accessibles", +"Han-lès-Juvigny", +"Han-sur-Lesse", +"Han-sur-Meuse", +"Han-sur-Nied", "Hanerau-Hademarschen", "Hangen-Weisheim", "Hangest-en-Santerre", "Hangest-sur-Somme", -"Han-lès-Juvigny", "Hannogne-Saint-Martin", "Hannogne-Saint-Rémy", -"Hannonville-sous-les-Côtes", "Hannonville-Suzémont", -"Han-sur-Lesse", -"Han-sur-Meuse", -"Han-sur-Nied", +"Hannonville-sous-les-Côtes", "Hantes-Wihéries", -"happe-chair", -"happe-chat", -"happe-foie", -"harai-goshi", -"haraï-goshi", -"hara-kiri", -"hara-kiris", -"hara-kiriser", -"hara-kiriser", "Haraucourt-sur-Seille", -"hard-discount", -"hard-discountisa", -"hard-discountisai", -"hard-discountisaient", -"hard-discountisais", -"hard-discountisait", -"hard-discountisâmes", -"hard-discountisant", -"hard-discountisas", -"hard-discountisasse", -"hard-discountisassent", -"hard-discountisasses", -"hard-discountisassiez", -"hard-discountisassions", -"hard-discountisât", -"hard-discountisâtes", -"hard-discountise", -"hard-discountisé", -"hard-discountisée", -"hard-discountisées", -"hard-discountisent", -"hard-discountiser", -"hard-discountisera", -"hard-discountiserai", -"hard-discountiseraient", -"hard-discountiserais", -"hard-discountiserait", -"hard-discountiseras", -"hard-discountisèrent", -"hard-discountiserez", -"hard-discountiseriez", -"hard-discountiserions", -"hard-discountiserons", -"hard-discountiseront", -"hard-discountises", -"hard-discountisés", -"hard-discountisez", -"hard-discountisiez", -"hard-discountisions", -"hard-discountisons", -"hard-discounts", "Hardecourt-aux-Bois", "Hardencourt-Cocherel", "Hardinxveld-Giessendam", -"hardi-petit", "Hardivillers-en-Vexin", "Hargarten-aux-Mines", "Hargeville-sur-Chée", -"harpe-guitare", -"harpe-luth", "Harréville-les-Chanteurs", "Hartennes-et-Taux", "Harth-Pöllnitz", "Hartmannsdorf-Reichenau", -"has-been", -"has-beens", "Hastière-Lavaux", "Hastière-par-delà", +"Haucourt-Moulaine", "Haucourt-en-Cambrésis", "Haucourt-la-Rigole", -"Haucourt-Moulaine", "Hauenstein-Ifenthal", "Haumont-lès-Lachaussée", "Haumont-près-Samogneux", "Hauptwil-Gottshaus", -"hausse-col", -"hausse-cols", -"hausse-pied", -"hausse-pieds", -"hausse-queue", -"Hautecourt-lès-Broville", +"Haut-Bocage", +"Haut-Clocher", +"Haut-Lieu", +"Haut-Loquin", +"Haut-Mauco", +"Haut-de-Bosdarros", +"Haut-du-Them-Château-Lambert", +"Haute-Amance", +"Haute-Avesnes", +"Haute-Goulaine", +"Haute-Isle", +"Haute-Kontz", +"Haute-Rivoire", +"Haute-Vigneulles", +"Haute-Épine", "Hautecourt-Romanèche", +"Hautecourt-lès-Broville", "Hautefage-la-Tour", -"Hautem-Sainte-Marguerite", "Hautem-Saint-Liévin", +"Hautem-Sainte-Marguerite", "Hautepierre-le-Châtelet", "Hauterive-la-Fresse", +"Hautes-Duyes", "Hauteville-Gondon", -"Hauteville-la-Guichard", -"Hauteville-lès-Dijon", "Hauteville-Lompnes", "Hauteville-Lompnés", +"Hauteville-la-Guichard", +"Hauteville-lès-Dijon", "Hauteville-sur-Fier", "Hauteville-sur-Mer", "Hauthem-Saint-Liévin", +"Hautot-Saint-Sulpice", "Hautot-l'Auvray", "Hautot-le-Vatois", -"Hautot-Saint-Sulpice", "Hautot-sur-Mer", "Hautot-sur-Seine", "Hautteville-Bocage", "Hautvillers-Ouville", "Havre-Saint-Pierrois", -"haye-le-comtois", "Haye-le-Comtois", -"haye-le-comtoise", "Haye-le-Comtoise", -"haye-le-comtoises", "Haye-le-Comtoises", -"Haÿ-les-Roses", "Hazerswoude-Dorp", "Hazerswoude-Rijndijk", +"Haÿ-les-Roses", "Hechtel-Eksel", "Heckelberg-Brunow", -"hecto-ohm", -"hecto-ohms", -"Hédé-Bazouges", "Heeswijk-Dinther", "Heeze-Leende", -"Heiltz-le-Hutier", -"Heiltz-le-Maurupt", "Heiltz-l'Evêque", "Heiltz-l'Évêque", +"Heiltz-le-Hutier", +"Heiltz-le-Maurupt", "Heining-lès-Bouzonville", "Heist-op-den-Berg", "Heist-sur-la-Montagne", @@ -12310,75 +5627,36 @@ FR_BASE_EXCEPTIONS = [ "Hellschen-Heringsand-Unterschaar", "Helmstadt-Bargen", "Hem-Hardinval", -"hémi-dodécaèdre", -"hémi-épiphyte", -"hémi-épiphytes", -"hémi-octaèdre", "Hem-Lenglet", "Hem-Monacu", "Hendecourt-lès-Cagnicourt", "Hendecourt-lès-Ransart", "Hendrik-Ido-Ambacht", -"Hénin-Beaumont", -"Hénin-sur-Cojeul", "Henri-Chapelle", "Henstedt-Ulzburg", -"hentai-gana", -"hépato-biliaire", -"hépato-cystique", -"hépato-cystiques", -"hépato-gastrique", -"hépato-gastrite", -"hépato-gastrites", -"herbe-à-cochon", -"herbe-au-bitume", -"herbe-aux-femmes-battues", -"herbe-aux-plaies", -"herbes-à-cochon", -"herbes-au-bitume", -"herbes-aux-femmes-battues", -"herbes-aux-plaies", -"herbes-aux-taupes", -"Herck-la-Ville", "Herck-Saint-Lambert", -"herd-book", +"Herck-la-Ville", "Herdwangen-Schönach", -"Héricourt-en-Caux", -"Héricourt-Saint-Samson", -"Héricourt-sur-Thérain", "Heringen-sur-Helme", -"Hérinnes-lez-Enghien", "Herlin-le-Sec", "Hermalle-sous-Argenteau", "Hermalle-sous-Huy", "Hermanville-sur-Mer", "Hermeton-sur-Meuse", -"Herméville-en-Woëvre", "Hermitage-Lorge", "Hermival-les-Vaux", +"Herméville-en-Woëvre", "Hernán-Pérez", -"héroï-comique", -"héroï-comiques", -"Hérouville-en-Vexin", -"Hérouville-Saint-Clair", "Herpy-l'Arlésienne", "Herren-Sulzbach", "Herrlisheim-près-Colmar", "Herschweiler-Pettersheim", "Hersfeld-Rotenburg", "Hersin-Coupigny", -"Héry-sur-Alby", "Herzebrock-Clarholz", -"Hesdigneul-lès-Béthune", "Hesdigneul-lès-Boulogne", +"Hesdigneul-lès-Béthune", "Hesdin-l'Abbé", -"hétéro-céphalophorie", -"hétéro-céphalophories", -"hétéro-épitaxie", -"hétéro-évaluation", -"hétéro-évaluations", -"hétéro-réparation", -"hétéro-réparations", "Hettange-Grande", "Heubécourt-Haricourt", "Heuchelheim-Klingen", @@ -12393,253 +5671,73 @@ FR_BASE_EXCEPTIONS = [ "Heuilley-sur-Saône", "Heume-l'Eglise", "Heume-l'Église", -"heure-homme", "Heure-le-Romain", "Heure-le-Tixhe", -"heure-lumière", -"heures-hommes", -"heures-lumière", -"heurte-pot", "Heusden-Zolder", -"hexa-core", -"hexa-cores", -"hexa-rotor", -"hexa-rotors", -"Hières-sur-Amby", "Hiers-Brouage", -"hi-fi", -"high-life", -"high-tech", "Higuères-Souye", -"hi-han", "Hilgertshausen-Tandern", -"himène-plume", "Hinzert-Pölert", -"hip-hop", -"hip-hopisa", -"hip-hopisai", -"hip-hopisaient", -"hip-hopisais", -"hip-hopisait", -"hip-hopisâmes", -"hip-hopisant", -"hip-hopisas", -"hip-hopisasse", -"hip-hopisassent", -"hip-hopisasses", -"hip-hopisassiez", -"hip-hopisassions", -"hip-hopisât", -"hip-hopisâtes", -"hip-hopise", -"hip-hopisé", -"hip-hopisée", -"hip-hopisées", -"hip-hopisent", -"hip-hopiser", -"hip-hopisera", -"hip-hopiserai", -"hip-hopiseraient", -"hip-hopiserais", -"hip-hopiserait", -"hip-hopiseras", -"hip-hopisèrent", -"hip-hopiserez", -"hip-hopiseriez", -"hip-hopiserions", -"hip-hopiserons", -"hip-hopiseront", -"hip-hopises", -"hip-hopisés", -"hip-hopisez", -"hip-hopisiez", -"hip-hopisions", -"hip-hopisons", -"hippocampe-feuillu", -"hippocampes-feuillus", "Hirz-Maulsbach", -"hispano-américain", -"hispano-américaine", -"hispano-américaines", -"hispano-américains", -"hispano-arabe", -"hispano-arabes", -"hispano-mauresque", -"hispano-moresque", -"hispano-moresques", -"histoire-géo", -"historico-culturelle", -"hitléro-trotskisme", -"hitléro-trotskiste", -"hit-parade", -"hit-parades", "Hiva-Oa", -"hoat-chi", +"Hières-sur-Amby", "Hochdorf-Assenheim", -"hoche-cul", -"hoche-culs", -"hoche-queue", -"Hô-Chi-Minh-Ville", "Hochstetten-Dhaun", "Hodenc-en-Bray", "Hodenc-l'Evêque", "Hodenc-l'Évêque", -"Hodeng-au-Bosc", "Hodeng-Hodenger", +"Hodeng-au-Bosc", "Hofstetten-Flüh", +"Hohen-Sülzen", "Hohenberg-Krusemark", "Hohenfels-Essingen", -"Höhenkirchen-Siegertsbrunn", "Hohenstein-Ernstthal", -"Hohen-Sülzen", -"Höhr-Grenzhausen", -"hokkaïdo-ken", -"hold-up", -"Hollande-du-Nord", -"Hollande-du-Sud", "Hollande-Méridionale", "Hollande-Septentrionale", +"Hollande-du-Nord", +"Hollande-du-Sud", "Hollern-Twielenfleth", "Hollogne-aux-Pierres", "Hollogne-sur-Geer", "Holstein-de-l'Est", "Hombourg-Budange", "Hombourg-Haut", -"Hôme-Chamondot", -"home-jacking", -"home-jackings", -"home-sitter", -"home-sitters", -"home-sitting", -"home-sittings", -"home-trainer", -"home-trainers", -"homme-animal", -"homme-chacal", -"homme-clé", -"homme-femme", -"homme-fourmi", -"homme-grenouille", -"homme-léopard", -"homme-loup", -"homme-mort", -"homme-morts", -"homme-objet", -"homme-orchestre", -"homme-robot", -"homme-sandwich", -"hommes-chacals", -"hommes-clés", -"hommes-femmes", -"hommes-fourmis", -"hommes-grenouilles", -"hommes-léopards", -"hommes-loups", -"hommes-objets", -"hommes-orchestres", -"hommes-robots", -"hommes-sandwiches", -"hommes-sandwichs", -"hommes-troncs", -"homme-tronc", -"homo-épitaxie", -"homo-épitaxies", -"honey-dew", -"Hong-Kong", -"hong-kongais", -"Hong-kongais", -"hong-kongaise", -"Hong-kongaise", -"hong-kongaises", -"Hong-kongaises", -"Honguemare-Guenouville", -"hon-hergeois", "Hon-Hergeois", -"hon-hergeoise", "Hon-Hergeoise", -"hon-hergeoises", "Hon-Hergeoises", "Hon-Hergies", +"Hong-Kong", +"Hong-kongais", +"Hong-kongaise", +"Hong-kongaises", +"Honguemare-Guenouville", "Honnecourt-sur-Escaut", "Honnécourt-sur-l'Escaut", "Honor-de-Cos", "Hoog-Baarlo", "Hoog-Caestert", -"Hoogezand-Sappemeer", "Hoog-Geldrop", "Hoog-Keppel", +"Hoogezand-Sappemeer", "Hoorebeke-Saint-Corneille", "Hoorebeke-Sainte-Marie", -"Hôpital-Camfrout", -"Hôpital-d'Orion", -"Hôpital-du-Grosbois", -"Hôpital-le-Grand", -"Hôpital-le-Mercier", -"Hôpital-Saint-Blaise", -"Hôpital-Saint-Lieffroy", -"Hôpital-sous-Rochefort", "Hoppstädten-Weiersbach", "Horbourg-Wihr", "Horion-Hozémont", "Horndon-on-the-Hill", "Hornow-Wadelsdorf", "Hornoy-le-Bourg", -"horo-kilométrique", -"horo-kilométriques", "Horrenbach-Buchen", -"hors-bord", -"hors-bords", -"hors-champ", -"hors-concours", -"hors-d'oeuvre", -"hors-d'œuvre", -"horse-ball", -"horse-guard", -"horse-guards", -"Hörselberg-Hainich", -"hors-fonds", -"hors-jeu", -"hors-jeux", -"hors-la-loi", -"hors-ligne", -"hors-lignes", -"hors-norme", -"hors-piste", -"hors-pistes", -"hors-sac", -"hors-série", -"hors-séries", -"hors-service", -"hors-sol", -"hors-sols", -"hors-sujet", -"hors-temps", -"hors-texte", -"hors-textes", "Horville-en-Ornois", "Hospitalet-du-Larzac", "Hospitalet-près-l'Andorre", "Hoste-Haut", -"hostello-flavien", "Hostello-Flavien", -"hostello-flavienne", "Hostello-Flavienne", -"hostello-flaviennes", "Hostello-Flaviennes", -"hostello-flaviens", "Hostello-Flaviens", -"hot-dog", -"hot-dogs", -"Hôtel-de-Ville", -"hôtel-Dieu", -"Hôtel-Dieu", -"Hôtellerie-de-Flée", -"hôtellerie-restauration", -"hôtels-Dieu", -"hot-melt", -"hot-melts", "Hotot-en-Auge", -"hot-plug", "Hottot-les-Bagues", "Houdain-lez-Bavay", "Houdelaucourt-sur-Othain", @@ -12648,181 +5746,82 @@ FR_BASE_EXCEPTIONS = [ "Houdeng-Gœgnies", "Houlbec-Cocherel", "Houlbec-près-le-Gros-Theil", -"houl'eau", "Houphouët-Boigny", "Houplin-Ancoisne", -"house-boats", -"Houtain-le-Val", -"Houtain-l'Évêque", -"Houtain-Saint-Siméon", "Hout-Blerick", +"Houtain-Saint-Siméon", +"Houtain-l'Évêque", +"Houtain-le-Val", "Houthalen-Helchteren", "Houville-en-Vexin", "Houville-la-Branche", "Houvin-Houvigneul", -"houx-frelon", -"houx-frelons", "Hoya-Gonzalo", "Huanne-Montmartin", "Hubert-Folie", "Huby-Saint-Leu", -"Huércal-Overa", -"Huétor-Tájar", "Hugleville-en-Caux", "Huilly-sur-Seille", -"huis-clos", "Huisnes-sur-Mer", "Huison-Longueville", "Huisseau-en-Beauce", "Huisseau-sur-Cosson", "Huisseau-sur-Mauves", -"huitante-neuf", -"huitante-neuvième", -"huitante-neuvièmes", -"huit-marsiste", -"huit-marsistes", -"huit-pieds", -"huit-reflets", -"huit-ressorts", "Humes-Jorquenay", -"hume-vent", -"huppe-col", "Hures-la-Parade", "Hurons-Wendat", -"huron-wendat", -"Husseren-les-Châteaux", "Husseren-Wesserling", +"Husseren-les-Châteaux", "Hussigny-Godbrange", -"hydrargyro-cyanate", -"hydrargyro-cyanates", -"hydraulico-pneumatique", -"hydro-aviation", -"hydro-aviations", -"hydro-avion", -"hydro-avions", -"hydro-électricité", -"hydro-électricités", -"hydro-électrique", -"hydro-électriques", -"hydro-ensemencement", -"hydro-ensemencements", -"hydro-météorologie", +"Huércal-Overa", +"Huétor-Tájar", "Hyencourt-le-Grand", "Hyencourt-le-Petit", -"hyène-garou", -"hyènes-garous", "Hyèvre-Magny", "Hyèvre-Paroisse", -"hyo-épiglottique", -"hyo-épiglottiques", -"hyo-pharyngien", -"hypo-centre", -"hypo-centres", -"hypo-iodeuse", -"hypo-iodeuses", -"hypo-iodeux", -"hypothético-déductif", -"hystéro-catalepsie", -"hystéro-catalepsies", -"hystéro-épilepsie", -"hystéro-épilepsies", +"Häg-Ehrsberg", +"Hédé-Bazouges", +"Hénin-Beaumont", +"Hénin-sur-Cojeul", +"Héricourt-Saint-Samson", +"Héricourt-en-Caux", +"Héricourt-sur-Thérain", +"Hérinnes-lez-Enghien", +"Hérouville-Saint-Clair", +"Hérouville-en-Vexin", +"Héry-sur-Alby", +"Hô-Chi-Minh-Ville", +"Hôme-Chamondot", +"Hôpital-Camfrout", +"Hôpital-Saint-Blaise", +"Hôpital-Saint-Lieffroy", +"Hôpital-d'Orion", +"Hôpital-du-Grosbois", +"Hôpital-le-Grand", +"Hôpital-le-Mercier", +"Hôpital-sous-Rochefort", +"Hôtel-Dieu", +"Hôtel-de-Ville", +"Hôtellerie-de-Flée", +"Höhenkirchen-Siegertsbrunn", +"Höhr-Grenzhausen", +"Hörselberg-Hainich", +"I-frame", +"II-VI", +"III-V", +"IS-IS", "Iamalo-Nénètsie", -"iatro-magique", -"iatro-magiques", -"ibéro-roman", -"i-butane", -"i-butanes", -"ice-belt", -"ice-belts", -"ice-berg", -"ice-bergs", -"ice-blink", -"ice-blinks", -"ice-bloc", -"ice-blocs", -"ice-cream", -"ice-creams", -"ice-foot", -"ice-foots", -"ice-rapt", -"ice-rapts", -"ice-table", -"ice-tables", -"ici-bas", "Idanha-a-Nova", "Idar-Oberstein", "Idaux-Mendy", -"idéal-type", -"idée-force", -"idée-maîtresse", -"idées-forces", -"idées-maîtresses", -"idio-électricité", -"idio-électrique", -"idio-électriques", "Idrac-Respaillès", "Ids-Saint-Roch", -"i.-e.", -"ifira-mele", -"ifira-meles", -"I-frame", "Igny-Comblizy", -"igny-marin", "Igny-Marin", -"igny-marine", "Igny-Marine", -"igny-marines", "Igny-Marines", -"igny-marins", "Igny-Marins", -"III-V", -"II-VI", -"Île-aux-Moines", -"Île-Bouchard", -"Île-d'Aix", -"Île-d'Anticosti", -"Île-d'Arz", -"Île-de-Batz", -"Île-de-Bréhat", "Ile-de-France", -"île-de-France", -"Île-de-France", -"Île-d'Elle", -"Île-de-Sein", -"Île-d'Houat", -"Île-d'Olonne", -"Île-du-Prince-Édouard", -"Île-d'Yeu", -"île-État", -"Île-Molène", -"iléo-cæcal", -"iléo-cæcale", -"iléo-cæcales", -"iléo-cæcaux", -"iléo-colique", -"iléo-coliques", -"iléos-meldois", -"Iléos-Meldois", -"iléos-meldoise", -"Iléos-Meldoise", -"iléos-meldoises", -"Iléos-Meldoises", -"île-prison", -"Île-Rousse", -"Île-Saint-Denis", -"Îles-de-la-Madeleine", -"îles-États", -"îles-prisons", -"île-tudiste", -"Île-Tudiste", -"île-tudistes", -"Île-Tudistes", -"Île-Tudy", -"iliaco-fémoral", -"iliaco-musculaire", -"ilio-pectiné", -"ilio-pubien", -"ilio-scrotal", "Ille-et-Vilaine", "Ille-sur-Têt", "Illeville-sur-Montfort", @@ -12832,198 +5831,82 @@ FR_BASE_EXCEPTIONS = [ "Illiers-l'Évêque", "Illkirch-Graffenstaden", "Illnau-Effretikon", -"ilo-dionysien", "Ilo-Dionysien", -"îlo-dionysien", -"Îlo-Dionysien", -"ilo-dionysienne", "Ilo-Dionysienne", -"Îlo-Dionysienne", -"ilo-dionysiennes", "Ilo-Dionysiennes", -"ilo-dionysiens", "Ilo-Dionysiens", -"image-gradient", -"imazaméthabenz-méthyl", -"immuno-pharmacologie", -"immuno-pharmacologies", -"impari-nervé", -"impari-nervié", -"impari-penné", -"impératrice-mère", -"impératrices-mères", -"import-export", -"in-12", -"in-12º", -"in-16", -"in-16º", -"in-18", -"in-18º", -"in-32", -"in-4", -"in-4º", -"in-4.º", -"in-4to", -"in-6", -"in-6º", -"in-8", -"in-8º", -"in-8.º", -"in-8vo", -"in-cent-vingt-huit", -"inch'allah", -"inch'Allah", -"Inch'allah", +"Iléos-Meldois", +"Iléos-Meldoise", +"Iléos-Meldoises", "Inch'Allah", +"Inch'allah", "Inchy-en-Artois", -"incito-moteur", -"incito-motricité", -"income-tax", -"indane-1,3-dione", -"inde-plate", -"india-océanisme", -"india-océanismes", -"in-dix-huit", -"in-douze", "Indre-et-Loire", -"in-duodecimo", -"in-fº", -"info-ballon", -"info-ballons", -"info-bulle", -"info-bulles", -"in-folio", -"ingénieur-conseil", -"ingénieur-docteur", -"ingénieure-conseil", -"ingénieures-conseils", -"ingénieur-maître", -"ingénieurs-conseils", -"ingénieurs-docteurs", -"ingénieurs-maîtres", +"Ingrandes-Le Fresne sur Loire", "Ingrandes-de-Touraine", -"in-huit", -"injonction-bâillon", "Injoux-Génissiat", -"in-manus", -"in-octavo", -"in-plano", -"in-plº", -"in-promptu", -"in-quarto", -"insecto-mortifère", -"insecto-mortifères", -"in-sedecimo", -"in-seize", -"in-six", -"inspecteur-chef", -"inspecteurs-chefs", -"insulino-dépendant", -"insulino-dépendante", -"insulino-dépendantes", -"insulino-dépendants", "Interlaken-Oberhasli", -"interno-médial", -"interro-négatif", -"intervertébro-costal", -"in-trente-deux", "Intville-la-Guétard", -"inuit-aléoute", -"inuit-aléoutes", "Inval-Boiron", -"in-vingt-quatre", -"in-vitro", "Inzinzac-Lochrist", -"iodo-borique", -"iodo-chlorure", -"iodosulfuron-méthyl-sodium", -"iowa-oto", -"iowa-otos", -"Î.-P.-É.", -"Iré-le-Sec", "Iruraiz-Gauna", -"ischio-anal", -"ischio-clitorien", -"ischio-fémoral", -"ischio-fémorale", -"ischio-fémorales", -"ischio-fémoraux", -"ischio-jambier", -"ischio-jambière", -"ischio-jambières", -"ischio-jambiers", -"ischio-périnéal", -"ischio-tibial", -"ischio-tibiaux", +"Iré-le-Sec", "Is-en-Bassigny", +"Is-sur-Tille", "Isigny-le-Buat", "Isigny-sur-Mer", -"IS-IS", "Isle-Adam", "Isle-Arné", "Isle-Aubigny", "Isle-Aumont", "Isle-Bouzon", -"Isle-d'Abeau", -"Isle-de-Noé", -"Isle-d'Espagnac", -"Isle-en-Dodon", -"Isle-et-Bardais", "Isle-Jourdain", "Isle-Saint-Georges", -"Isles-les-Meldeuses", -"Isles-lès-Villenoy", +"Isle-Vertois", +"Isle-d'Abeau", +"Isle-d'Espagnac", +"Isle-de-Noé", +"Isle-en-Dodon", +"Isle-et-Bardais", "Isle-sous-Montréal", -"Isles-sur-Suippe", -"Isle-sur-la-Sorgue", -"Isle-sur-le-Doubs", "Isle-sur-Marne", "Isle-sur-Serein", -"Isle-Vertois", +"Isle-sur-la-Sorgue", +"Isle-sur-le-Doubs", +"Isles-les-Meldeuses", +"Isles-lès-Villenoy", +"Isles-sur-Suippe", "Isolaccio-di-Fiumorbo", -"isoxadifen-éthyl", -"israélo-syrienne", "Issancourt-et-Rumel", "Issoudun-Létrieix", -"Is-sur-Tille", -"Issy-les-Moulineaux", "Issy-l'Evêque", "Issy-l'Évêque", -"istro-roumain", +"Issy-les-Moulineaux", "Ithorots-Olhaïby", "Ivano-Fracena", "Ivoy-le-Petit", "Ivoy-le-Pré", "Ivoz-Ramet", -"ivre-mort", -"ivre-morte", -"ivres-mortes", -"ivres-morts", "Ivry-en-Montagne", "Ivry-la-Bataille", "Ivry-le-Temple", "Ivry-sur-Seine", "Izaut-de-l'Hôtel", -"Izel-lès-Equerchin", -"Izel-lès-Équerchin", -"Izel-lès-Hameau", "Izel-les-Hameaux", +"Izel-lès-Equerchin", +"Izel-lès-Hameau", +"Izel-lès-Équerchin", "Izon-la-Bruisse", +"J-pop", +"J-rock", +"JAX-RPC", +"JAX-RS", "Jabreilles-les-Bordes", -"jack-russell", "Jacob-Bellecombette", "Jagny-sous-Bois", -"jaguar-garou", -"jaguars-garous", -"jaï-alaï", -"jaï-alaïs", "Jailly-les-Moulins", "Jaligny-sur-Besbre", -"jambon-beurre", -"jambon-des-jardiniers", -"jambons-des-jardiniers", "Jammu-et-Cachemire", -"jam-sessions", "Jandrain-Jandrenouille", "Janville-sur-Juine", "Jard-sur-Mer", @@ -13033,98 +5916,27 @@ FR_BASE_EXCEPTIONS = [ "Jassans-Riottier", "Jau-Dignac-et-Loirac", "Jaunay-Clan", -"jaunay-clanais", "Jaunay-Clanais", -"jaunay-clanaise", "Jaunay-Clanaise", -"jaunay-clanaises", "Jaunay-Clanaises", "Jaunay-Marigny", "Javerlhac-et-la-Chapelle-Saint-Robert", "Javron-les-Chapelles", -"JAX-RPC", -"JAX-RS", "Jeannois-Mitissien", -"jeans-de-gand", -"jeans-de-janten", -"je-m'en-fichisme", -"je-m'en-fichismes", -"je-m'en-fichiste", -"je-m'en-fichistes", -"je-m'en-foutisme", -"je-m'en-foutismes", -"je-m'en-foutiste", -"je-m'en-foutistes", "Jemeppe-sur-Sambre", -"je-ne-sais-quoi", -"jérôme-boschisme", -"jérôme-boschismes", -"Jésus-Christ", -"jet-set", -"jet-sets", -"jet-settisa", -"jet-settisai", -"jet-settisaient", -"jet-settisais", -"jet-settisait", -"jet-settisâmes", -"jet-settisant", -"jet-settisas", -"jet-settisasse", -"jet-settisassent", -"jet-settisasses", -"jet-settisassiez", -"jet-settisassions", -"jet-settisât", -"jet-settisâtes", -"jet-settise", -"jet-settisé", -"jet-settisée", -"jet-settisées", -"jet-settisent", -"jet-settiser", -"jet-settisera", -"jet-settiserai", -"jet-settiseraient", -"jet-settiserais", -"jet-settiserait", -"jet-settiseras", -"jet-settisèrent", -"jet-settiserez", -"jet-settiseriez", -"jet-settiserions", -"jet-settiserons", -"jet-settiseront", -"jet-settises", -"jet-settisés", -"jet-settisez", -"jet-settisiez", -"jet-settisions", -"jet-settisons", -"jet-stream", -"jet-streams", -"jette-bouts", "Jettingen-Scheppach", -"Jeu-les-Bois", "Jeu-Maloches", -"jeu-malochois", "Jeu-Malochois", -"jeu-malochoise", "Jeu-Malochoise", -"jeu-malochoises", "Jeu-Malochoises", -"jeu-parti", +"Jeu-les-Bois", "Jeux-lès-Bard", "Ji-hu", "Ji-hun", -"jiu-jitsu", "Jodoigne-Souveraine", "John-Bull", "Joigny-sur-Meuse", -"joint-venture", -"joint-ventures", "Joinville-le-Pont", -"joli-bois", "Jollain-Merlin", "Jonchery-sur-Suippe", "Jonchery-sur-Vesle", @@ -13134,24 +5946,14 @@ FR_BASE_EXCEPTIONS = [ "Jonzier-Epagny", "Jonzier-Épagny", "Jorat-Menthue", -"Jouars-Pontchartrain", -"Joué-du-Bois", -"Joué-du-Plain", -"Joué-en-Charnie", -"Joué-Étiau", -"Joué-l'Abbé", -"Joué-lès-Tours", -"Joué-sur-Erdre", -"Jouet-sur-l'Aubois", -"jour-homme", -"jour-lumière", -"Jours-en-Vaux", -"jours-hommes", -"Jours-lès-Baigneux", -"jours-lumière", "Jou-sous-Monjou", +"Jouars-Pontchartrain", +"Jouet-sur-l'Aubois", +"Jours-en-Vaux", +"Jours-lès-Baigneux", "Joux-la-Ville", "Jouxtens-Mézery", +"Jouy-Mauvoisin", "Jouy-aux-Arches", "Jouy-en-Argonne", "Jouy-en-Josas", @@ -13160,48 +5962,22 @@ FR_BASE_EXCEPTIONS = [ "Jouy-le-Moutier", "Jouy-le-Potier", "Jouy-lès-Reims", -"Jouy-Mauvoisin", "Jouy-sous-Thelle", "Jouy-sur-Eure", "Jouy-sur-Morin", -"J-pop", -"J-rock", -"j't'aime", +"Joué-du-Bois", +"Joué-du-Plain", +"Joué-en-Charnie", +"Joué-l'Abbé", +"Joué-lès-Tours", +"Joué-sur-Erdre", +"Joué-Étiau", "Juan-les-Pins", "Juaye-Mondaye", "Jubbega-Schurega", -"Jû-Belloc", -"judéo-allemand", -"judéo-alsacien", -"judéo-arabe", -"judéo-arabes", -"judéo-asiatique", -"judéo-bolchévisme", -"judéo-centrisme", -"judéo-chrétien", -"judéo-chrétienne", -"judéo-chrétiennes", -"judéo-chrétiens", -"judéo-christianisme", -"judéo-christiano-islamique", -"judéo-christiano-islamiques", -"judéo-christiano-musulman", -"judéo-espagnol", -"judéo-espagnole", -"judéo-espagnoles", -"judéo-espagnols", -"judéo-iranien", -"judéo-libyen", -"judéo-lybien", -"judéo-maçonnique", -"judéo-maçonniques", -"judéo-musulman", -"judéo-musulmans", -"judéo-nazi", -"judéo-nazis", "Jugeals-Nazareth", "Jugon-les-Lacs", -"juǀ'hoan", +"Jugon-les-Lacs - Commune nouvelle", "Juif-Errant", "Juifs-Errants", "Juigné-Béné", @@ -13209,74 +5985,56 @@ FR_BASE_EXCEPTIONS = [ "Juigné-sur-Loire", "Juigné-sur-Sarthe", "Juillac-le-Coq", -"ju-jitsu", -"ju-ju", -"juke-box", -"juke-boxes", -"Jully-lès-Buxy", -"jully-sarçois", "Jully-Sarçois", -"jully-sarçoise", "Jully-Sarçoise", -"jully-sarçoises", "Jully-Sarçoises", +"Jully-lès-Buxy", "Jully-sur-Sarce", "Jumilhac-le-Grand", -"junk-food", -"junk-foods", -"jupe-culotte", -"jupes-culottes", "Jupille-sur-Meuse", -"juridico-politique", -"juridico-politiques", -"jusque-là", "Jussecourt-Minecourt", "Jussy-Champagne", "Jussy-le-Chaudrier", -"juste-à-temps", -"juste-au-corps", "Justine-Herbigny", +"Juvigny Val d'Andaine", +"Juvigny-Val-d'Andaine", "Juvigny-en-Perthois", -"Juvigny-les-Vallées", "Juvigny-le-Tertre", +"Juvigny-les-Vallées", "Juvigny-sous-Andaine", "Juvigny-sur-Loison", "Juvigny-sur-Orne", "Juvigny-sur-Seulles", -"Juvigny-Val-d'Andaine", "Juvincourt-et-Damary", "Juvisy-sur-Orge", -"juxta-position", -"juxta-positions", -"Juzet-de-Luchon", "Juzet-d'Izaut", +"Juzet-de-Luchon", +"Jésus-Christ", +"Jû-Belloc", +"K-POP", +"K-Pop", +"K-bis", +"K-pop", +"K-way", +"K-ways", "Kaala-Gomen", "Kabardino-Balkarie", "Kaiser-Wilhelm-Koog", "Kalenborn-Scheuern", -"kali'na", "Kamerik-Houtdijken", "Kamerik-Mijzijde", "Kamp-Bornhofen", +"Kamp-Lintfort", "Kamperzeedijk-Oost", "Kamperzeedijk-West", -"Kamp-Lintfort", "Kani-Kéli", -"kan-kan", -"kan-kans", -"kansai-ben", "Kapel-Avezaath", -"Kapellen-Drusweiler", "Kapelle-op-den-Bos", +"Kapellen-Drusweiler", "Kappel-Grafenhausen", -"karachay-balkar", -"karafuto-ken", -"kara-gueuz", -"kara-kalpak", "Kara-Koum", "Karangasso-Sambla", "Karangasso-Vigué", -"karatchaï-balkar", "Karatchaïévo-Tcherkassie", "Karbow-Vietlübbe", "Karlsdorf-Neuthard", @@ -13286,14 +6044,8 @@ FR_BASE_EXCEPTIONS = [ "Kastel-Staadt", "Katlenburg-Lindau", "Kaysersberg-Vignoble", -"K-bis", -"Kédange-sur-Canner", "Kelpen-Oler", -"kem's", "Kenz-Küstrow", -"kérato-pharyngien", -"kérato-staphylin", -"kérato-staphylins", "Kerckom-lez-Saint-Trond", "Kergrist-Moëlou", "Kerk-Avezaath", @@ -13308,81 +6060,14 @@ FR_BASE_EXCEPTIONS = [ "Kersbeek-Miskom", "Kessel-Eik", "Kessel-Lo", -"khambo-lama", -"khambo-lamas", -"khatti-chérif", -"khatti-chérifs", -"khi-carré", -"khi-carrés", -"khi-deux", "Kiel-Windeweer", -"kif-kif", -"kilo-électrons-volts", -"kiloélectrons-volts", -"kilo-électron-volt", -"kiloélectron-volt", -"kilo-électron-volts", -"kiloélectron-volts", -"kilogramme-force", -"kilogramme-poids", -"kilogrammes-force", -"kilogrammes-poids", -"kilomètre-heure", -"kilomètres-heure", -"kilo-ohm", -"kilo-ohms", -"kin-ball", "Kingston-sur-Tamise", "Kingston-upon-Hull", "Kingston-upon-Thames", -"kino-congolais", "Kino-Congolais", -"kip-kap", -"kip-kaps", "Kirkby-in-Ashfield", "Kirrwiller-Bosselshausen", "Kirsch-lès-Sierck", -"kirsch-wasser", -"kirsch-wassers", -"kiss-in", -"kite-surf", -"kite-surfa", -"kite-surfai", -"kite-surfaient", -"kite-surfais", -"kite-surfait", -"kite-surfâmes", -"kite-surfant", -"kite-surfas", -"kite-surfasse", -"kite-surfassent", -"kite-surfasses", -"kite-surfassiez", -"kite-surfassions", -"kite-surfât", -"kite-surfâtes", -"kite-surfe", -"kite-surfé", -"kite-surfent", -"kite-surfer", -"kite-surfera", -"kite-surferai", -"kite-surferaient", -"kite-surferais", -"kite-surferait", -"kite-surferas", -"kite-surfèrent", -"kite-surferez", -"kite-surferiez", -"kite-surferions", -"kite-surferons", -"kite-surferont", -"kite-surfers", -"kite-surfes", -"kite-surfez", -"kite-surfiez", -"kite-surfions", -"kite-surfons", "Kizil-Arvat", "Klazienaveen-Noord", "Klein-Amsterdam", @@ -13391,127 +6076,715 @@ FR_BASE_EXCEPTIONS = [ "Klein-Delfgauw", "Klein-Doenrade", "Klein-Dongen", -"Kleine-Brogel", -"Kleine-Spouwen", "Klein-Overleek", "Klein-Ulsda", "Klein-Valkenisse", "Klein-Wetsinge", "Klein-Winternheim", "Klein-Zundert", +"Kleine-Brogel", +"Kleine-Spouwen", "Kleßen-Görne", "Klooster-Lidlum", "Klosters-Serneus", -"knicker-bocker", -"knicker-bockers", -"knock-out", -"knock-outa", -"knock-outai", -"knock-outaient", -"knock-outais", -"knock-outait", -"knock-outâmes", -"knock-outant", -"knock-outas", -"knock-outasse", -"knock-outassent", -"knock-outasses", -"knock-outassiez", -"knock-outassions", -"knock-outât", -"knock-outâtes", -"knock-oute", -"knock-outé", -"knock-outée", -"knock-outées", -"knock-outent", -"knock-outer", -"knock-outera", -"knock-outerai", -"knock-outeraient", -"knock-outerais", -"knock-outerait", -"knock-outeras", -"knock-outèrent", -"knock-outerez", -"knock-outeriez", -"knock-outerions", -"knock-outerons", -"knock-outeront", -"knock-outes", -"knock-outés", -"knock-outez", -"knock-outiez", -"knock-outions", -"knock-outons", -"knock-outs", "Knokke-Heist", "Knopp-Labach", "Kobern-Gondorf", -"Kœur-la-Grande", -"Kœur-la-Petite", "Kohren-Sahlis", -"Kölln-Reisiek", "Komki-Ipala", -"Königsbach-Stein", -"Königshain-Wiederau", "Korbeek-Dijle", "Korbeek-Lo", "Korntal-Münchingen", -"ko-soto-gake", "Kottweiler-Schwanden", -"kouan-hoa", -"kouign-aman", -"kouign-amann", -"kouign-amanns", -"kouign-amans", -"K-pop", -"K-Pop", -"K-POP", "Kradolf-Schönenberg", -"krav-naga", "Kreba-Neudorf", "Kreimbach-Kaulbach", -"krésoxim-méthyl", "Kröppelshagen-Fahrendorf", "Kuhlen-Wendorf", -"kung-fu", -"k-voisinage", -"k-voisinages", -"kwan-li-so", -"k-way", -"K-way", -"k-ways", -"K-ways", "KwaZulu-Natal", "Kyzyl-Arvat", +"Kœur-la-Grande", +"Kœur-la-Petite", +"Kédange-sur-Canner", +"Kölln-Reisiek", +"Königsbach-Stein", +"Königshain-Wiederau", +"Kœur-la-Grande", +"Kœur-la-Petite", +"L'Abergement-Clémenciat", +"L'Abergement-Sainte-Colombe", +"L'Abergement-de-Cuisery", +"L'Abergement-de-Varey", +"L'Absie", +"L'Aigle", +"L'Aiguillon", +"L'Aiguillon-sur-Mer", +"L'Aiguillon-sur-Vie", +"L'Ajoupa-Bouillon", +"L'Albenc", +"L'Albère", +"L'Arbresle", +"L'Argentière-la-Bessée", +"L'Aubépin", +"L'Escale", +"L'Escarène", +"L'Estréchure", +"L'Habit", +"L'Haÿ-les-Roses", +"L'Herbergement", +"L'Herm", +"L'Hermenault", +"L'Hermitage", +"L'Honor-de-Cos", +"L'Horme", +"L'Hosmes", +"L'Hospitalet", +"L'Hospitalet-du-Larzac", +"L'Hospitalet-près-l'Andorre", +"L'Houmeau", +"L'Huisserie", +"L'Hôme-Chamondot", +"L'Hôpital", +"L'Hôpital-Saint-Blaise", +"L'Hôpital-Saint-Lieffroy", +"L'Hôpital-d'Orion", +"L'Hôpital-du-Grosbois", +"L'Hôpital-le-Grand", +"L'Hôpital-le-Mercier", +"L'Hôpital-sous-Rochefort", +"L'Hôtellerie", +"L'Hôtellerie-de-Flée", +"L'Isle-Adam", +"L'Isle-Arné", +"L'Isle-Bouzon", +"L'Isle-Jourdain", +"L'Isle-d'Abeau", +"L'Isle-d'Espagnac", +"L'Isle-de-Noé", +"L'Isle-en-Dodon", +"L'Isle-sur-Serein", +"L'Isle-sur-la-Sorgue", +"L'Isle-sur-le-Doubs", +"L'Orbrie", +"L'Oudon", +"L'Union", +"L'Écaille", +"L'Échelle", +"L'Échelle-Saint-Aurin", +"L'Écouvotte", +"L'Église-aux-Bois", +"L'Éguille", +"L'Épine", +"L'Épine-aux-Bois", +"L'Étang-Bertrand", +"L'Étang-Salé", +"L'Étang-Vergy", +"L'Étang-la-Ville", +"L'Étoile", +"L'Étrat", +"L'Île-Bouchard", +"L'Île-Rousse", +"L'Île-Saint-Denis", +"L'Île-d'Elle", +"L'Île-d'Olonne", +"L'Île-d'Yeu", +"L-aminoacide", +"L-aminoacides", +"L-flampropisopropyl", +"L-glycéraldéhyde", +"LGBTI-friendly", +"LGBTI-phobie", +"LGBTI-phobies", +"La Balme-d'Épy", +"La Balme-de-Sillingy", +"La Balme-de-Thuy", +"La Balme-les-Grottes", +"La Barre-de-Monts", +"La Barre-de-Semilly", +"La Barthe-de-Neste", +"La Basse-Vaivre", +"La Bastide-Clairence", +"La Bastide-Pradines", +"La Bastide-Puylaurent", +"La Bastide-Solages", +"La Bastide-d'Engras", +"La Bastide-de-Besplas", +"La Bastide-de-Bousignac", +"La Bastide-de-Lordat", +"La Bastide-de-Sérou", +"La Bastide-des-Jourdans", +"La Bastide-du-Salat", +"La Bastide-sur-l'Hers", +"La Baule-Escoublac", +"La Baume-Cornillane", +"La Baume-d'Hostun", +"La Baume-de-Transit", +"La Bazoche-Gouet", +"La Bazoge-Montpinçon", +"La Bazouge-de-Chemeré", +"La Bazouge-des-Alleux", +"La Bazouge-du-Désert", +"La Bernerie-en-Retz", +"La Besseyre-Saint-Mary", +"La Boissière-d'Ans", +"La Boissière-de-Montaigu", +"La Boissière-des-Landes", +"La Boissière-du-Doré", +"La Boissière-en-Gâtine", +"La Boissière-École", +"La Bollène-Vésubie", +"La Bonneville-sur-Iton", +"La Bosse-de-Bretagne", +"La Bourdinière-Saint-Loup", +"La Breille-les-Pins", +"La Bretonnière-la-Claye", +"La Brosse-Montceaux", +"La Bruère-sur-Loir", +"La Brée-les-Bains", +"La Bussière-sur-Ouche", +"La Bâtie-Montgascon", +"La Bâtie-Montsaléon", +"La Bâtie-Neuve", +"La Bâtie-Rolland", +"La Bâtie-Vieille", +"La Bâtie-des-Fonds", +"La Bégude-de-Mazenc", +"La Bénisson-Dieu", +"La Cadière-d'Azur", +"La Cadière-et-Cambo", +"La Caillère-Saint-Hilaire", +"La Capelle-Balaguier", +"La Capelle-Bleys", +"La Capelle-Bonance", +"La Capelle-et-Masmolène", +"La Capelle-lès-Boulogne", +"La Celle-Condé", +"La Celle-Dunoise", +"La Celle-Guenand", +"La Celle-Saint-Avant", +"La Celle-Saint-Cloud", +"La Celle-Saint-Cyr", +"La Celle-en-Morvan", +"La Celle-les-Bordes", +"La Celle-sous-Chantemerle", +"La Celle-sous-Gouzon", +"La Celle-sur-Loire", +"La Celle-sur-Morin", +"La Celle-sur-Nièvre", +"La Chaise-Baudouin", +"La Chaise-Dieu", +"La Chaize-Giraud", +"La Chaize-le-Vicomte", +"La Chapelle-Achard", +"La Chapelle-Agnon", +"La Chapelle-Anthenaise", +"La Chapelle-Aubareil", +"La Chapelle-Baloue", +"La Chapelle-Bayvel", +"La Chapelle-Bertin", +"La Chapelle-Bertrand", +"La Chapelle-Biche", +"La Chapelle-Blanche", +"La Chapelle-Blanche-Saint-Martin", +"La Chapelle-Bouëxic", +"La Chapelle-Bâton", +"La Chapelle-Chaussée", +"La Chapelle-Craonnaise", +"La Chapelle-Cécelin", +"La Chapelle-Enchérie", +"La Chapelle-Erbrée", +"La Chapelle-Faucher", +"La Chapelle-Felcourt", +"La Chapelle-Forainvilliers", +"La Chapelle-Fortin", +"La Chapelle-Gaceline", +"La Chapelle-Gaugain", +"La Chapelle-Gauthier", +"La Chapelle-Geneste", +"La Chapelle-Glain", +"La Chapelle-Gonaguet", +"La Chapelle-Grésignac", +"La Chapelle-Hareng", +"La Chapelle-Hermier", +"La Chapelle-Heulin", +"La Chapelle-Hugon", +"La Chapelle-Hullin", +"La Chapelle-Huon", +"La Chapelle-Iger", +"La Chapelle-Janson", +"La Chapelle-Lasson", +"La Chapelle-Launay", +"La Chapelle-Laurent", +"La Chapelle-Marcousse", +"La Chapelle-Montabourlet", +"La Chapelle-Montbrandeix", +"La Chapelle-Montligeon", +"La Chapelle-Montlinard", +"La Chapelle-Montmartin", +"La Chapelle-Montmoreau", +"La Chapelle-Montreuil", +"La Chapelle-Moulière", +"La Chapelle-Moutils", +"La Chapelle-Naude", +"La Chapelle-Neuve", +"La Chapelle-Onzerain", +"La Chapelle-Orthemale", +"La Chapelle-Palluau", +"La Chapelle-Pouilloux", +"La Chapelle-Rablais", +"La Chapelle-Rainsouin", +"La Chapelle-Rambaud", +"La Chapelle-Réanville", +"La Chapelle-Saint-André", +"La Chapelle-Saint-Aubert", +"La Chapelle-Saint-Aubin", +"La Chapelle-Saint-Fray", +"La Chapelle-Saint-Géraud", +"La Chapelle-Saint-Jean", +"La Chapelle-Saint-Laud", +"La Chapelle-Saint-Laurent", +"La Chapelle-Saint-Laurian", +"La Chapelle-Saint-Luc", +"La Chapelle-Saint-Martial", +"La Chapelle-Saint-Martin", +"La Chapelle-Saint-Martin-en-Plaine", +"La Chapelle-Saint-Maurice", +"La Chapelle-Saint-Mesmin", +"La Chapelle-Saint-Ouen", +"La Chapelle-Saint-Quillain", +"La Chapelle-Saint-Rémy", +"La Chapelle-Saint-Sauveur", +"La Chapelle-Saint-Sulpice", +"La Chapelle-Saint-Sépulcre", +"La Chapelle-Saint-Ursin", +"La Chapelle-Saint-Étienne", +"La Chapelle-Souëf", +"La Chapelle-Taillefert", +"La Chapelle-Thireuil", +"La Chapelle-Thouarault", +"La Chapelle-Thècle", +"La Chapelle-Thémer", +"La Chapelle-Urée", +"La Chapelle-Vaupelteigne", +"La Chapelle-Vendômoise", +"La Chapelle-Vicomtesse", +"La Chapelle-Viel", +"La Chapelle-Villars", +"La Chapelle-au-Mans", +"La Chapelle-au-Moine", +"La Chapelle-au-Riboul", +"La Chapelle-aux-Bois", +"La Chapelle-aux-Brocs", +"La Chapelle-aux-Chasses", +"La Chapelle-aux-Choux", +"La Chapelle-aux-Filtzméens", +"La Chapelle-aux-Lys", +"La Chapelle-aux-Naux", +"La Chapelle-aux-Saints", +"La Chapelle-d'Abondance", +"La Chapelle-d'Alagnon", +"La Chapelle-d'Aligné", +"La Chapelle-d'Angillon", +"La Chapelle-d'Armentières", +"La Chapelle-d'Aunainville", +"La Chapelle-d'Aurec", +"La Chapelle-de-Bragny", +"La Chapelle-de-Brain", +"La Chapelle-de-Guinchay", +"La Chapelle-de-Surieu", +"La Chapelle-de-la-Tour", +"La Chapelle-des-Fougeretz", +"La Chapelle-des-Marais", +"La Chapelle-des-Pots", +"La Chapelle-devant-Bruyères", +"La Chapelle-du-Bard", +"La Chapelle-du-Bois", +"La Chapelle-du-Bois-des-Faulx", +"La Chapelle-du-Bourgay", +"La Chapelle-du-Châtelard", +"La Chapelle-du-Lou-du-Lac", +"La Chapelle-du-Mont-de-France", +"La Chapelle-du-Mont-du-Chat", +"La Chapelle-du-Noyer", +"La Chapelle-en-Lafaye", +"La Chapelle-en-Serval", +"La Chapelle-en-Valgaudémar", +"La Chapelle-en-Vercors", +"La Chapelle-en-Vexin", +"La Chapelle-la-Reine", +"La Chapelle-lès-Luxeuil", +"La Chapelle-près-Sées", +"La Chapelle-sous-Brancion", +"La Chapelle-sous-Dun", +"La Chapelle-sous-Orbais", +"La Chapelle-sous-Uchon", +"La Chapelle-sur-Aveyron", +"La Chapelle-sur-Chézy", +"La Chapelle-sur-Coise", +"La Chapelle-sur-Dun", +"La Chapelle-sur-Erdre", +"La Chapelle-sur-Furieuse", +"La Chapelle-sur-Loire", +"La Chapelle-sur-Oreuse", +"La Chapelle-sur-Oudon", +"La Chapelle-sur-Usson", +"La Charité-sur-Loire", +"La Chartre-sur-le-Loir", +"La Chaussée-Saint-Victor", +"La Chaussée-Tirancourt", +"La Chaussée-d'Ivry", +"La Chaussée-sur-Marne", +"La Chaux-du-Dombief", +"La Chaux-en-Bresse", +"La Chaze-de-Peyre", +"La Châtre-Langlin", +"La Cluse-et-Mijoux", +"La Colle-sur-Loup", +"La Combe-de-Lancey", +"La Condamine-Châtelard", +"La Couarde-sur-Mer", +"La Cour-Marigny", +"La Couture-Boussey", +"La Croisille-sur-Briance", +"La Croix-Avranchin", +"La Croix-Blanche", +"La Croix-Comtesse", +"La Croix-Helléan", +"La Croix-Valmer", +"La Croix-aux-Bois", +"La Croix-aux-Mines", +"La Croix-de-la-Rochette", +"La Croix-du-Perche", +"La Croix-en-Brie", +"La Croix-en-Champagne", +"La Croix-en-Touraine", +"La Croix-sur-Gartempe", +"La Croix-sur-Ourcq", +"La Croix-sur-Roudoule", +"La Côte-Saint-André", +"La Côte-d'Arbroz", +"La Côte-en-Couzan", +"La Digne-d'Amont", +"La Digne-d'Aval", +"La Fage-Montivernoux", +"La Fage-Saint-Julien", +"La Fare-en-Champsaur", +"La Fare-les-Oliviers", +"La Faute-sur-Mer", +"La Ferrière-Airoux", +"La Ferrière-Bochard", +"La Ferrière-Béchet", +"La Ferrière-au-Doyen", +"La Ferrière-aux-Étangs", +"La Ferrière-de-Flée", +"La Ferrière-en-Parthenay", +"La Ferrière-sur-Risle", +"La Ferté-Alais", +"La Ferté-Beauharnais", +"La Ferté-Bernard", +"La Ferté-Chevresis", +"La Ferté-Gaucher", +"La Ferté-Hauterive", +"La Ferté-Imbault", +"La Ferté-Loupière", +"La Ferté-Macé", +"La Ferté-Milon", +"La Ferté-Saint-Aubin", +"La Ferté-Saint-Cyr", +"La Ferté-Saint-Samson", +"La Ferté-Vidame", +"La Ferté-Villeneuil", +"La Ferté-en-Ouche", +"La Ferté-sous-Jouarre", +"La Ferté-sur-Chiers", +"La Folletière-Abenon", +"La Fontaine-Saint-Martin", +"La Forest-Landerneau", +"La Forêt-Fouesnant", +"La Forêt-Sainte-Croix", +"La Forêt-de-Tessé", +"La Forêt-du-Parc", +"La Forêt-du-Temple", +"La Forêt-le-Roi", +"La Forêt-sur-Sèvre", +"La Fosse-Corduan", +"La Foye-Monjault", +"La Fresnaie-Fayel", +"La Frette-sur-Seine", +"La Garde-Adhémar", +"La Garde-Freinet", +"La Garenne-Colombes", +"La Gonterie-Boulouneix", +"La Grand-Combe", +"La Grand-Croix", +"La Grande-Fosse", +"La Grande-Motte", +"La Grande-Paroisse", +"La Grande-Résie", +"La Grande-Verrière", +"La Gripperie-Saint-Symphorien", +"La Grève-sur-Mignon", +"La Grée-Saint-Laurent", +"La Guerche-de-Bretagne", +"La Guerche-sur-l'Aubois", +"La Haie-Fouassière", +"La Haie-Traversaine", +"La Haute-Beaume", +"La Haute-Maison", +"La Haye-Aubrée", +"La Haye-Bellefond", +"La Haye-Malherbe", +"La Haye-Pesnel", +"La Haye-Saint-Sylvestre", +"La Haye-d'Ectot", +"La Haye-de-Calleville", +"La Haye-de-Routot", +"La Haye-du-Theil", +"La Haye-le-Comte", +"La Houssaye-Béranger", +"La Houssaye-en-Brie", +"La Jaille-Yvon", +"La Jarrie-Audouin", +"La Jonchère-Saint-Maurice", +"La Lande-Chasles", +"La Lande-Patry", +"La Lande-Saint-Léger", +"La Lande-Saint-Siméon", +"La Lande-d'Airou", +"La Lande-de-Fronsac", +"La Lande-de-Goult", +"La Lande-de-Lougé", +"La Lande-sur-Drôme", +"La Lanterne-et-les-Armonts", +"La Loge-Pomblin", +"La Loge-aux-Chèvres", +"La Londe-les-Maures", +"La Louptière-Thénard", +"La Louvière-Lauragais", +"La Lucerne-d'Outremer", +"La Madelaine-sous-Montreuil", +"La Madeleine-Bouvet", +"La Madeleine-Villefrouin", +"La Madeleine-de-Nonancourt", +"La Madeleine-sur-Loing", +"La Magdelaine-sur-Tarn", +"La Maison-Dieu", +"La Marolle-en-Sologne", +"La Mazière-aux-Bons-Hommes", +"La Meilleraie-Tillay", +"La Meilleraye-de-Bretagne", +"La Membrolle-sur-Choisille", +"La Monnerie-le-Montel", +"La Mothe-Achard", +"La Mothe-Saint-Héray", +"La Motte-Chalancon", +"La Motte-Fanjas", +"La Motte-Feuilly", +"La Motte-Fouquet", +"La Motte-Saint-Jean", +"La Motte-Saint-Martin", +"La Motte-Servolex", +"La Motte-Ternant", +"La Motte-Tilly", +"La Motte-d'Aigues", +"La Motte-d'Aveillans", +"La Motte-de-Galaure", +"La Motte-du-Caire", +"La Motte-en-Bauges", +"La Motte-en-Champsaur", +"La Mure-Argens", +"La Neuve-Grange", +"La Neuve-Lyre", +"La Neuvelle-lès-Lure", +"La Neuvelle-lès-Scey", +"La Neuveville-devant-Lépanges", +"La Neuveville-sous-Châtenois", +"La Neuveville-sous-Montfort", +"La Neuville-Bosmont", +"La Neuville-Chant-d'Oisel", +"La Neuville-Garnier", +"La Neuville-Housset", +"La Neuville-Roy", +"La Neuville-Saint-Pierre", +"La Neuville-Sire-Bernard", +"La Neuville-Vault", +"La Neuville-au-Pont", +"La Neuville-aux-Bois", +"La Neuville-aux-Joûtes", +"La Neuville-aux-Larris", +"La Neuville-d'Aumont", +"La Neuville-du-Bosc", +"La Neuville-en-Beine", +"La Neuville-en-Hez", +"La Neuville-en-Tourne-à-Fuy", +"La Neuville-lès-Bray", +"La Neuville-lès-Dorengt", +"La Neuville-lès-Wasigny", +"La Neuville-sur-Essonne", +"La Neuville-sur-Oudeuil", +"La Neuville-sur-Ressons", +"La Neuville-à-Maire", +"La Nocle-Maulaix", +"La Noë-Blanche", +"La Noë-Poulain", +"La Palud-sur-Verdon", +"La Penne-sur-Huveaune", +"La Penne-sur-l'Ouvèze", +"La Petite-Boissière", +"La Petite-Fosse", +"La Petite-Marche", +"La Petite-Pierre", +"La Petite-Raon", +"La Petite-Verrière", +"La Plaine-des-Palmistes", +"La Plaine-sur-Mer", +"La Poterie-Cap-d'Antifer", +"La Poterie-Mathieu", +"La Proiselière-et-Langle", +"La Queue-en-Brie", +"La Queue-les-Yvelines", +"La Rivière-Drugeon", +"La Rivière-Enverse", +"La Rivière-Saint-Sauveur", +"La Rivière-de-Corps", +"La Robine-sur-Galabre", +"La Roche-Bernard", +"La Roche-Blanche", +"La Roche-Canillac", +"La Roche-Chalais", +"La Roche-Clermault", +"La Roche-Derrien", +"La Roche-Guyon", +"La Roche-Mabile", +"La Roche-Maurice", +"La Roche-Morey", +"La Roche-Noire", +"La Roche-Posay", +"La Roche-Rigault", +"La Roche-Vanneau", +"La Roche-Vineuse", +"La Roche-de-Glun", +"La Roche-de-Rame", +"La Roche-des-Arnauds", +"La Roche-en-Brenil", +"La Roche-l'Abeille", +"La Roche-sur-Foron", +"La Roche-sur-Grane", +"La Roche-sur-Yon", +"La Roche-sur-le-Buis", +"La Rochebeaucourt-et-Argentine", +"La Rochette-du-Buis", +"La Ronde-Haye", +"La Roque-Alric", +"La Roque-Baignard", +"La Roque-Esclapon", +"La Roque-Gageac", +"La Roque-Sainte-Marguerite", +"La Roque-d'Anthéron", +"La Roque-en-Provence", +"La Roque-sur-Cèze", +"La Roque-sur-Pernes", +"La Roquette-sur-Siagne", +"La Roquette-sur-Var", +"La Rue-Saint-Pierre", +"La Répara-Auriples", +"La Résie-Saint-Martin", +"La Salette-Fallavaux", +"La Salle-en-Beaumont", +"La Salle-les-Alpes", +"La Salvetat-Belmontet", +"La Salvetat-Lauragais", +"La Salvetat-Peyralès", +"La Salvetat-Saint-Gilles", +"La Salvetat-sur-Agout", +"La Sauvetat-de-Savères", +"La Sauvetat-du-Dropt", +"La Sauvetat-sur-Lède", +"La Sauzière-Saint-Jean", +"La Selle-Craonnaise", +"La Selle-Guerchaise", +"La Selle-en-Coglès", +"La Selle-en-Hermoy", +"La Selle-en-Luitré", +"La Selle-la-Forge", +"La Selle-sur-le-Bied", +"La Serre-Bussière-Vieille", +"La Seyne-sur-Mer", +"La Suze-sur-Sarthe", +"La Séauve-sur-Semène", +"La Terrasse-sur-Dorlay", +"La Teste-de-Buch", +"La Tour-Blanche", +"La Tour-Saint-Gelin", +"La Tour-d'Aigues", +"La Tour-d'Auvergne", +"La Tour-de-Salvagny", +"La Tour-de-Sçay", +"La Tour-du-Crieu", +"La Tour-du-Meix", +"La Tour-du-Pin", +"La Tour-en-Jarez", +"La Tour-sur-Orb", +"La Tourette-Cabardès", +"La Tranche-sur-Mer", +"La Trinité-Porhoët", +"La Trinité-Surzur", +"La Trinité-de-Réville", +"La Trinité-de-Thouberville", +"La Trinité-des-Laitiers", +"La Trinité-du-Mont", +"La Trinité-sur-Mer", +"La Vacheresse-et-la-Rouillie", +"La Vacquerie-et-Saint-Martin-de-Castries", +"La Valette-du-Var", +"La Valla-en-Gier", +"La Valla-sur-Rochefort", +"La Vallée-Mulâtre", +"La Vallée-au-Blé", +"La Vendue-Mignot", +"La Vespière-Friardel", +"La Vicomté-sur-Rance", +"La Vieille-Loye", +"La Vieille-Lyre", +"La Vieux-Rue", +"La Ville-Dieu-du-Temple", +"La Ville-aux-Bois", +"La Ville-aux-Bois-lès-Dizy", +"La Ville-aux-Bois-lès-Pontavert", +"La Ville-aux-Clercs", +"La Ville-aux-Dames", +"La Ville-du-Bois", +"La Ville-sous-Orbais", +"La Ville-ès-Nonais", +"La Villedieu-du-Clain", +"La Villedieu-en-Fontenette", +"La Villeneuve-Bellenoye-et-la-Maize", +"La Villeneuve-au-Châtelot", +"La Villeneuve-au-Chêne", +"La Villeneuve-en-Chevrie", +"La Villeneuve-les-Convers", +"La Villeneuve-lès-Charleville", +"La Villeneuve-sous-Thury", +"La Voulte-sur-Rhône", +"La Vraie-Croix", +"La-Fertois", +"La-Fertoise", +"La-Fertoises", "Laag-Caestert", "Laag-Keppel", "Laag-Nieuwkoop", "Laag-Soeren", -"Laà-Mondrans", "Labarthe-Bleys", "Labarthe-Inard", "Labarthe-Rivière", "Labarthe-sur-Lèze", -"là-bas", "Labastide-Beauvoir", "Labastide-Castel-Amouroux", -"Labastide-Cézéracq", "Labastide-Chalosse", "Labastide-Clairence", "Labastide-Clermont", -"Labastide-d'Anjou", -"Labastide-d'Armagnac", -"Labastide-de-Juvinas", -"Labastide-de-Lévis", +"Labastide-Cézéracq", "Labastide-Dénat", -"Labastide-de-Penne", -"Labastide-de-Virac", -"Labastide-du-Haut-Mont", -"Labastide-du-Temple", -"Labastide-du-Vert", -"Labastide-en-Val", "Labastide-Esparbairenque", "Labastide-Gabausse", "Labastide-Marnhac", @@ -13523,49 +6796,60 @@ FR_BASE_EXCEPTIONS = [ "Labastide-Saint-Pierre", "Labastide-Saint-Sernin", "Labastide-Savès", -"Labastide-sur-Bésorgues", "Labastide-Villefranche", +"Labastide-d'Anjou", +"Labastide-d'Armagnac", +"Labastide-de-Juvinas", +"Labastide-de-Lévis", +"Labastide-de-Penne", +"Labastide-de-Virac", +"Labastide-du-Haut-Mont", +"Labastide-du-Temple", +"Labastide-du-Vert", +"Labastide-en-Val", +"Labastide-sur-Bésorgues", "Labatie-d'Andaure", "Labatut-Rivière", -"Labécède-Lauragais", -"Labergement-du-Navois", "Labergement-Foigney", +"Labergement-Sainte-Marie", +"Labergement-du-Navois", "Labergement-lès-Auxonne", "Labergement-lès-Seurre", -"Labergement-Sainte-Marie", "Labessière-Candeil", "Labets-Biscay", -"lab-ferment", -"lab-ferments", +"Laboissière-Saint-Martin", "Laboissière-en-Santerre", "Laboissière-en-Thelle", -"Laboissière-Saint-Martin", "Labruyère-Dorsa", -"lac-à-l'épaule", +"Labécède-Lauragais", +"Lac-Beauportois", +"Lac-Bouchettien", +"Lac-Carréen", +"Lac-Etcheminois", +"Lac-Humquien", +"Lac-Poulinois", +"Lac-Saguayen", +"Lac-aux-Sables", +"Lac-des-Rouges-Truites", +"Lac-ou-Villers", +"Lac-Édouard", "Lacam-d'Ourcet", "Lacapelle-Barrès", "Lacapelle-Biron", "Lacapelle-Cabanac", -"Lacapelle-del-Fraisse", "Lacapelle-Livron", "Lacapelle-Marival", "Lacapelle-Pinet", "Lacapelle-Ségalar", "Lacapelle-Viescamp", +"Lacapelle-del-Fraisse", "Lacarry-Arhan-Charritte-de-Haut", -"Lac-aux-Sables", -"Lac-Beauportois", -"Lac-Bouchettien", -"Lac-Carréen", -"Lac-des-Rouges-Truites", -"Lac-Édouard", -"Lac-Etcheminois", "Lachamp-Raphaël", -"Lachapelle-aux-Pots", "Lachapelle-Auzac", -"Lachapelle-en-Blaisy", "Lachapelle-Graillouse", "Lachapelle-Saint-Pierre", +"Lachapelle-aux-Pots", +"Lachapelle-en-Blaisy", "Lachapelle-sous-Aubenas", "Lachapelle-sous-Chanéac", "Lachapelle-sous-Chaux", @@ -13573,77 +6857,46 @@ FR_BASE_EXCEPTIONS = [ "Lachapelle-sous-Rougemont", "Lachaussée-du-Bois-d'Ecu", "Lachaussée-du-Bois-d'Écu", -"lache-bras", -"lâcher-tout", -"Lac-Humquien", -"lac-laque", -"lac-laques", -"là-contre", "Lacougotte-Cadoul", "Lacour-d'Arcenay", "Lacourt-Saint-Pierre", -"Lac-ou-Villers", -"Lac-Poulinois", -"lacrima-christi", -"lacrima-Christi", "Lacrima-Christi", "Lacroix-Barrez", "Lacroix-Falgarde", "Lacroix-Saint-Ouen", "Lacroix-sur-Meuse", -"lacryma-christi", -"lacryma-Christi", "Lacryma-Christi", -"Lac-Saguayen", -"lacs-à-l'épaule", -"lacto-végétarisme", -"lacto-végétarismes", -"là-dedans", -"là-delez", "Ladern-sur-Lauquet", -"là-dessous", -"là-dessus", "Ladevèze-Rivière", "Ladevèze-Ville", "Ladignac-le-Long", "Ladignac-sur-Rondelles", "Ladoix-Serrigny", "Ladoye-sur-Seille", -"laemmer-geier", -"laemmer-geiers", -"læmmer-geyer", -"læmmer-geyers", -"Laethem-Sainte-Marie", "Laethem-Saint-Martin", +"Laethem-Sainte-Marie", "Lafage-sur-Sombre", "Laferté-sur-Amance", "Laferté-sur-Aube", -"la-fertois", -"La-Fertois", -"la-fertoise", -"La-Fertoise", -"la-fertoises", -"La-Fertoises", "Lafeuillade-en-Vézie", "Laffite-Toupière", -"Lafitte-sur-Lot", "Lafitte-Vigordane", +"Lafitte-sur-Lot", "Lafresguimont-Saint-Martin", -"Lagarde-d'Apt", "Lagarde-Enval", "Lagarde-Hachan", -"Lagardelle-sur-Lèze", "Lagarde-Paréol", +"Lagarde-d'Apt", "Lagarde-sur-le-Né", +"Lagardelle-sur-Lèze", "Lagnicourt-Marcel", "Lagny-le-Sec", "Lagny-sur-Marne", -"Lagrâce-Dieu", -"Lagraulet-du-Gers", "Lagraulet-Saint-Nicolas", +"Lagraulet-du-Gers", +"Lagrâce-Dieu", "Laguian-Mazous", "Laguinge-Restoue", -"là-haut", "Lahaye-Saint-Romain", "Lahitte-Toupière", "Lahn-Dill", @@ -13651,21 +6904,9 @@ FR_BASE_EXCEPTIONS = [ "Lailly-en-Val", "Laines-aux-Bois", "Lainville-en-Vexin", -"laissée-pour-compte", -"laissées-pour-compte", -"laissé-pour-compte", -"laisser-aller", -"laisser-allers", -"laisser-courre", -"laisser-faire", -"laisser-sur-place", -"laissés-pour-compte", -"laissez-faire", -"laissez-passer", -"Laître-sous-Amance", +"Laissac-Sévérac l'Église", "Laize-Clinchamps", "Laize-la-Ville", -"la-la-la", "Lalande-de-Pomerol", "Lalande-en-Son", "Lalanne-Arqué", @@ -13681,67 +6922,38 @@ FR_BASE_EXCEPTIONS = [ "Lamarque-Rustaing", "Lamazière-Basse", "Lamazière-Haute", -"lambda-cyhalothrine", -"Lambres-lès-Aire", "Lambres-lez-Aire", "Lambres-lez-Douai", +"Lambres-lès-Aire", "Lamenay-sur-Loire", -"L-aminoacide", -"L-aminoacides", "Lamonzie-Montastruc", "Lamonzie-Saint-Martin", "Lamothe-Capdeville", "Lamothe-Cassel", "Lamothe-Cumont", -"Lamothe-en-Blaisy", "Lamothe-Fénelon", "Lamothe-Goas", "Lamothe-Landerron", "Lamothe-Montravel", +"Lamothe-en-Blaisy", "Lamotte-Beuvron", "Lamotte-Brebière", "Lamotte-Buleux", -"Lamotte-du-Rhône", "Lamotte-Warfusée", +"Lamotte-du-Rhône", "Lampaul-Guimiliau", "Lampaul-Plouarzel", "Lampaul-Ploudalmézeau", -"lampes-tempête", -"lampe-tempête", -"l-amphétamine", -"lampris-lune", "Lamure-sur-Azergues", -"lance-amarres", -"lance-balles", -"lance-bombe", -"lance-bombes", -"lance-flamme", -"lance-flammes", -"lance-fusée", -"lance-fusées", -"lance-grenade", -"lance-grenades", -"lance-missile", -"lance-missiles", -"lance-patates", -"lance-pierre", -"lance-pierres", -"lance-roquette", -"lance-roquettes", -"lance-torpille", -"lance-torpilles", "Lanches-Saint-Hilaire", "Lanciego-Lantziego", "Lancken-Granitz", -"Lançon-Provence", "Lande-de-Libourne", "Landelles-et-Coupigny", "Landerrouet-sur-Ségur", +"Landes-Vieilles-et-Neuves", "Landes-le-Gaulois", "Landes-sur-Ajon", -"Landes-Vieilles-et-Neuves", -"land-ice", -"land-ices", "Landifay-et-Bertaignemont", "Landouzy-la-Cour", "Landouzy-la-Ville", @@ -13756,54 +6968,37 @@ FR_BASE_EXCEPTIONS = [ "Laneuveville-devant-Nancy", "Laneuveville-en-Saulnois", "Laneuveville-lès-Lorquin", -"Laneuville-à-Rémy", "Laneuville-au-Bois", "Laneuville-au-Pont", "Laneuville-au-Rupt", "Laneuville-sur-Meuse", +"Laneuville-à-Rémy", "Langemark-Poelkapelle", "Langenleuba-Niederhain", "Langrolay-sur-Rance", "Langrune-sur-Mer", -"langue-de-boeuf", -"langue-de-chat", -"langue-de-moineau", -"langue-de-serpent", -"langue-de-vache", "Languedoc-Roussillon", "Languedoc-Roussillon-Midi-Pyrénées", -"langues-de-boeuf", -"langues-de-chat", -"langues-de-vache", -"langues-toit", -"langue-toit", "Languevoisin-Quiquery", "Lanitz-Hassel-Tal", -"Lanne-en-Barétous", "Lanne-Soubiran", -"lanne-soubiranais", "Lanne-Soubiranais", -"lanne-soubiranaise", "Lanne-Soubiranaise", -"lanne-soubiranaises", "Lanne-Soubiranaises", +"Lanne-en-Barétous", "Lannoy-Cuillère", "Lanques-sur-Rognon", -"Lansen-Schönau", "Lans-en-Vercors", -"Lanslebourg-Mont-Cenis", "Lans-l'Hermitage", +"Lansen-Schönau", +"Lanslebourg-Mont-Cenis", "Lantenne-Vertière", "Lanty-sur-Aube", +"Lançon-Provence", "Lapanouse-de-Cernon", "Laperrière-sur-Saône", "Lapeyrouse-Fossat", "Lapeyrouse-Mornay", -"lapin-garou", -"lapins-garous", -"lapis-lazuli", -"là-pour-ça", -"lapu-lapu", "Laragne-Montéglin", "Larceveau-Arros-Cibits", "Lardier-et-Valença", @@ -13811,23 +7006,21 @@ FR_BASE_EXCEPTIONS = [ "Largny-sur-Automne", "Larians-et-Munans", "Larivière-Arnoncourt", -"larme-de-Job", -"larmes-de-Job", "Larmor-Baden", "Larmor-Plage", -"Laroche-près-Feyt", "Laroche-Saint-Cydroine", +"Laroche-près-Feyt", +"Laroque-Timbaut", +"Laroque-d'Olmes", "Laroque-de-Fa", "Laroque-des-Albères", "Laroque-des-Arcs", -"Laroque-d'Olmes", -"Laroque-Timbaut", "Larribar-Sorhapuru", "Larrivière-Saint-Savin", "Larroque-Engalin", "Larroque-Saint-Sernin", -"Larroque-sur-l'Osse", "Larroque-Toirac", +"Larroque-sur-l'Osse", "Lasarte-Oria", "Lascellas-Ponzano", "Lasne-Chapelle-Saint-Lambert", @@ -13836,7 +7029,6 @@ FR_BASE_EXCEPTIONS = [ "Lasserre-de-Prouille", "Lasseube-Propre", "Lathus-Saint-Rémy", -"Lâ-Todin", "Latouille-Lentillac", "Latour-Bas-Elne", "Latour-de-Carol", @@ -13845,101 +7037,63 @@ FR_BASE_EXCEPTIONS = [ "Latrecey-Ormoy-sur-Aube", "Lattre-Saint-Quentin", "Lau-Balagnas", -"lau-balutin", "Lau-Balutin", -"lau-balutine", "Lau-Balutine", -"lau-balutines", "Lau-Balutines", -"lau-balutins", "Lau-Balutins", "Laucha-sur-Unstrut", "Lauda-Königshofen", "Laudio-Llodio", "Laudun-l'Ardoise", "Laufen-Uhwiesen", -"launay-villersois", "Launay-Villersois", -"launay-villersoise", "Launay-Villersoise", -"launay-villersoises", "Launay-Villersoises", "Launay-Villiers", "Launois-sur-Vence", "Laurac-en-Vivarais", "Laure-Minervois", -"laurier-cerise", -"laurier-rose", -"laurier-sauce", -"lauriers-cerises", -"lauriers-roses", -"lauriers-tins", -"laurier-tarte", -"laurier-thym", -"laurier-tin", "Lauwin-Planque", "Laux-Montaux", "Laval-Atger", +"Laval-Morency", +"Laval-Pradel", +"Laval-Roquecezière", +"Laval-Saint-Roman", "Laval-d'Aix", "Laval-d'Aurelle", "Laval-de-Cère", -"laval-de-cérois", "Laval-de-Cérois", -"laval-de-céroise", "Laval-de-Céroise", -"laval-de-céroises", "Laval-de-Céroises", "Laval-du-Tarn", "Laval-en-Brie", "Laval-en-Laonnois", "Laval-le-Prieuré", -"Laval-Morency", -"Laval-Pradel", -"Laval-Roquecezière", -"Laval-Saint-Roman", "Laval-sur-Doulon", "Laval-sur-Luzège", "Laval-sur-Tourbe", "Laval-sur-Vologne", "Lavancia-Epercy", -"Lavans-lès-Dole", -"Lavans-lès-Saint-Claude", -"lavans-quingeois", "Lavans-Quingeois", -"lavans-quingeoise", "Lavans-Quingeoise", -"lavans-quingeoises", "Lavans-Quingeoises", "Lavans-Quingey", -"Lavans-sur-Valouse", "Lavans-Vuillafans", -"Lavault-de-Frétoy", -"Lavault-Sainte-Anne", +"Lavans-lès-Dole", +"Lavans-lès-Saint-Claude", +"Lavans-sur-Valouse", "Lavau-sur-Loire", +"Lavault-Sainte-Anne", +"Lavault-de-Frétoy", "Lavaux-Oron", "Lavaux-Sainte-Anne", "Lavaveix-les-Mines", -"lave-auto", -"lave-autos", -"lavé-de-vert", -"lave-glace", "Lavelanet-de-Comminges", "Laveline-devant-Bruyères", "Laveline-du-Houx", -"lave-linge", -"lave-linges", -"lave-main", -"lave-mains", "Laveno-Mombello", -"lave-pont", -"lave-ponts", "Lavernose-Lacasse", -"lavés-de-vert", -"lave-tête", -"lave-têtes", -"laveuse-sécheuse", -"lave-vaisselle", -"lave-vaisselles", "Lavey-Morcles", "Laville-aux-Bois", "Lavilleneuve-au-Roi", @@ -13948,122 +7102,438 @@ FR_BASE_EXCEPTIONS = [ "Lavoûte-sur-Loire", "Lawarde-Mauger-l'Hortoy", "Lay-Lamidou", -"Layrac-sur-Tarn", "Lay-Saint-Christophe", "Lay-Saint-Remy", +"Layrac-sur-Tarn", "Lays-sur-le-Doubs", -"lazur-apatite", -"lazur-apatites", -"Léa-Lisa", -"lease-back", -"leather-jacket", -"lèche-botta", -"lèche-bottai", -"lèche-bottaient", -"lèche-bottais", -"lèche-bottait", -"lèche-bottâmes", -"lèche-bottant", -"lèche-bottas", -"lèche-bottasse", -"lèche-bottassent", -"lèche-bottasses", -"lèche-bottassiez", -"lèche-bottassions", -"lèche-bottât", -"lèche-bottâtes", -"lèche-botte", -"lèche-botté", -"lèche-bottée", -"lèche-bottées", -"lèche-bottent", -"lèche-botter", -"lèche-bottera", -"lèche-botterai", -"lèche-botteraient", -"lèche-botterais", -"lèche-botterait", -"lèche-botteras", -"lèche-bottèrent", -"lèche-botterez", -"lèche-botteriez", -"lèche-botterions", -"lèche-botterons", -"lèche-botteront", -"lèche-bottes", -"lèche-bottés", -"lèche-bottez", -"lèche-bottiez", -"lèche-bottions", -"lèche-bottons", -"lèche-cul", -"lèche-culs", -"lèche-vitrine", -"lèche-vitrines", -"lecteur-graveur", -"lecteurs-graveurs", -"Lédas-et-Penthiès", -"Leers-et-Fosteau", +"Laà-Mondrans", +"Laître-sous-Amance", +"Le Ban-Saint-Martin", +"Le Bar-sur-Loup", +"Le Bec-Hellouin", +"Le Bec-Thomas", +"Le Bellay-en-Vexin", +"Le Bignon-Mirabeau", +"Le Bignon-du-Maine", +"Le Blanc-Mesnil", +"Le Bois-Hellain", +"Le Bois-Plage-en-Ré", +"Le Bois-Robert", +"Le Bois-d'Oingt", +"Le Bosc-Renoult", +"Le Bosc-Roger-en-Roumois", +"Le Bouchet-Mont-Charvin", +"Le Bouchet-Saint-Nicolas", +"Le Bouchon-sur-Saulx", +"Le Boulay-Morin", +"Le Boullay-Mivoye", +"Le Boullay-Thierry", +"Le Boullay-les-Deux-Églises", +"Le Bourg-Dun", +"Le Bourg-Saint-Léonard", +"Le Bourg-d'Hem", +"Le Bourg-d'Iré", +"Le Bourg-d'Oisans", +"Le Bourget-du-Lac", +"Le Bourgneuf-la-Forêt", +"Le Bousquet-d'Orb", +"Le Breil-sur-Mérize", +"Le Breuil-Bernard", +"Le Breuil-en-Auge", +"Le Breuil-en-Bessin", +"Le Breuil-sur-Couze", +"Le Brouilh-Monbert", +"Le Buisson-de-Cadouin", +"Le Bû-sur-Rouvres", +"Le Cannet-des-Maures", +"Le Castellard-Mélan", +"Le Cateau-Cambrésis", +"Le Caule-Sainte-Beuve", +"Le Chaffaut-Saint-Jurson", +"Le Chambon-Feugerolles", +"Le Chambon-sur-Lignon", +"Le Champ-Saint-Père", +"Le Champ-de-la-Pierre", +"Le Champ-près-Froges", +"Le Château-d'Almenêches", +"Le Château-d'Oléron", +"Le Châtelet-en-Brie", +"Le Châtelet-sur-Meuse", +"Le Châtelet-sur-Retourne", +"Le Châtelet-sur-Sormonne", +"Le Châtenet-en-Dognon", +"Le Cloître-Pleyben", +"Le Cloître-Saint-Thégonnec", +"Le Collet-de-Dèze", +"Le Coudray-Macouard", +"Le Coudray-Montceaux", +"Le Coudray-Saint-Germer", +"Le Coudray-sur-Thelle", +"Le Fay-Saint-Quentin", +"Le Freney-d'Oisans", +"Le Fresne-Camilly", +"Le Fresne-Poret", +"Le Frestoy-Vaux", +"Le Gault-Perche", +"Le Gault-Saint-Denis", +"Le Gault-Soigny", +"Le Genest-Saint-Isle", +"Le Grand-Bornand", +"Le Grand-Bourg", +"Le Grand-Celland", +"Le Grand-Lemps", +"Le Grand-Lucé", +"Le Grand-Madieu", +"Le Grand-Pressigny", +"Le Grand-Quevilly", +"Le Grand-Serre", +"Le Grand-Village-Plage", +"Le Grau-du-Roi", +"Le Gué-d'Alleré", +"Le Gué-de-Longroi", +"Le Gué-de-Velluire", +"Le Gué-de-la-Chaîne", +"Le Haut-Corlay", +"Le Hommet-d'Arthenay", +"Le Housseau-Brétignolles", +"Le Hérie-la-Viéville", +"Le Kremlin-Bicêtre", +"Le Lac-d'Issarlès", +"Le Lardin-Saint-Lazare", +"Le Lauzet-Ubaye", +"Le Lion-d'Angers", +"Le Loroux-Bottereau", +"Le Louroux-Béconnais", +"Le Malzieu-Forain", +"Le Malzieu-Ville", +"Le Marais-la-Chapelle", +"Le Mas-d'Agenais", +"Le Mas-d'Artige", +"Le Mas-d'Azil", +"Le Mas-de-Tence", +"Le Masnau-Massuguiès", +"Le May-sur-Èvre", +"Le Mayet-d'École", +"Le Mayet-de-Montagne", +"Le Meix-Saint-Epoing", +"Le Meix-Tiercelin", +"Le Mesnil-Adelée", +"Le Mesnil-Amand", +"Le Mesnil-Amelot", +"Le Mesnil-Amey", +"Le Mesnil-Aubert", +"Le Mesnil-Aubry", +"Le Mesnil-Auzouf", +"Le Mesnil-Benoist", +"Le Mesnil-Caussois", +"Le Mesnil-Conteville", +"Le Mesnil-Durdent", +"Le Mesnil-Esnard", +"Le Mesnil-Eudes", +"Le Mesnil-Eury", +"Le Mesnil-Fuguet", +"Le Mesnil-Garnier", +"Le Mesnil-Gilbert", +"Le Mesnil-Guillaume", +"Le Mesnil-Hardray", +"Le Mesnil-Herman", +"Le Mesnil-Jourdain", +"Le Mesnil-Lieubray", +"Le Mesnil-Mauger", +"Le Mesnil-Ozenne", +"Le Mesnil-Patry", +"Le Mesnil-Rainfray", +"Le Mesnil-Robert", +"Le Mesnil-Rogues", +"Le Mesnil-Rouxelin", +"Le Mesnil-Réaume", +"Le Mesnil-Saint-Denis", +"Le Mesnil-Saint-Firmin", +"Le Mesnil-Simon", +"Le Mesnil-Thomas", +"Le Mesnil-Théribus", +"Le Mesnil-Tôve", +"Le Mesnil-Vigot", +"Le Mesnil-Villeman", +"Le Mesnil-Villement", +"Le Mesnil-Véneron", +"Le Mesnil-au-Grain", +"Le Mesnil-au-Val", +"Le Mesnil-en-Thelle", +"Le Mesnil-le-Roi", +"Le Mesnil-sous-Jumièges", +"Le Mesnil-sur-Blangy", +"Le Mesnil-sur-Bulles", +"Le Mesnil-sur-Oger", +"Le Minihic-sur-Rance", +"Le Molay-Littry", +"Le Monastier-sur-Gazeille", +"Le Monestier-du-Percy", +"Le Mont-Dieu", +"Le Mont-Saint-Adrien", +"Le Mont-Saint-Michel", +"Le Monteil-au-Vicomte", +"Le Monêtier-les-Bains", +"Le Morne-Rouge", +"Le Morne-Vert", +"Le Moulinet-sur-Solin", +"Le Mée-sur-Seine", +"Le Ménil-Broût", +"Le Ménil-Bérard", +"Le Ménil-Ciboult", +"Le Ménil-Guyon", +"Le Ménil-Scelleur", +"Le Ménil-Vicomte", +"Le Ménil-de-Briouze", +"Le Mêle-sur-Sarthe", +"Le Nouvion-en-Thiérache", +"Le Noyer-en-Ouche", +"Le Palais-sur-Vienne", +"Le Pas-Saint-l'Homer", +"Le Pavillon-Sainte-Julie", +"Le Perray-en-Yvelines", +"Le Perreux-sur-Marne", +"Le Petit-Bornand-les-Glières", +"Le Petit-Celland", +"Le Petit-Fougeray", +"Le Petit-Mercey", +"Le Petit-Pressigny", +"Le Petit-Quevilly", +"Le Pian-Médoc", +"Le Pian-sur-Garonne", +"Le Pin-Murelet", +"Le Pin-au-Haras", +"Le Pin-la-Garenne", +"Le Plan-de-la-Tour", +"Le Plessier-Huleu", +"Le Plessier-Rozainvillers", +"Le Plessier-sur-Bulles", +"Le Plessier-sur-Saint-Just", +"Le Plessis-Belleville", +"Le Plessis-Bouchard", +"Le Plessis-Brion", +"Le Plessis-Dorin", +"Le Plessis-Feu-Aussoux", +"Le Plessis-Gassot", +"Le Plessis-Grammoire", +"Le Plessis-Grimoult", +"Le Plessis-Grohan", +"Le Plessis-Hébert", +"Le Plessis-Lastelle", +"Le Plessis-Luzarches", +"Le Plessis-Patte-d'Oie", +"Le Plessis-Placy", +"Le Plessis-Pâté", +"Le Plessis-Robinson", +"Le Plessis-Sainte-Opportune", +"Le Plessis-Trévise", +"Le Plessis-aux-Bois", +"Le Plessis-l'Échelle", +"Le Plessis-l'Évêque", +"Le Poiré-sur-Velluire", +"Le Poiré-sur-Vie", +"Le Poizat-Lalleyriat", +"Le Pont-Chrétien-Chabenet", +"Le Pont-de-Beauvoisin", +"Le Pont-de-Claix", +"Le Port-Marly", +"Le Poujol-sur-Orb", +"Le Poët-Célard", +"Le Poët-Laval", +"Le Poët-Sigillat", +"Le Poët-en-Percip", +"Le Pré-Saint-Gervais", +"Le Pré-d'Auge", +"Le Puy-Notre-Dame", +"Le Puy-Sainte-Réparade", +"Le Puy-en-Velay", +"Le Péage-de-Roussillon", +"Le Quesnel-Aubry", +"Le Quesnoy-en-Artois", +"Le Relecq-Kerhuon", +"Le Revest-les-Eaux", +"Le Rouget-Pers", +"Le Rousset-Marizy", +"Le Sap-André", +"Le Sappey-en-Chartreuse", +"Le Sauze-du-Lac", +"Le Sel-de-Bretagne", +"Le Taillan-Médoc", +"Le Tartre-Gaudran", +"Le Temple-de-Bretagne", +"Le Temple-sur-Lot", +"Le Tertre-Saint-Denis", +"Le Theil-Nolent", +"Le Theil-de-Bretagne", +"Le Theil-en-Auge", +"Le Thil-Riberpré", +"Le Thoult-Trosnay", +"Le Thuit de l'Oison", +"Le Tilleul-Lambert", +"Le Tilleul-Othon", +"Le Torp-Mesnil", +"Le Touquet-Paris-Plage", +"Le Tour-du-Parc", +"Le Tremblay-Omonville", +"Le Tremblay-sur-Mauldre", +"Le Val d'Hazey", +"Le Val d'Ocre", +"Le Val-David", +"Le Val-Saint-Germain", +"Le Val-Saint-Père", +"Le Val-Saint-Éloi", +"Le Val-d'Ajol", +"Le Val-d'Esnoms", +"Le Val-de-Gouhenans", +"Le Val-de-Guéblange", +"Le Vanneau-Irleau", +"Le Verdon-sur-Mer", +"Le Vernet-Sainte-Marguerite", +"Le Vieil-Dampierre", +"Le Vieil-Évreux", +"Le Vieux-Bourg", +"Le Vieux-Cérier", +"Le Vieux-Marché", +"Le Vivier-sur-Mer", "Leers-Nord", -"Lées-Athas", +"Leers-et-Fosteau", "Leeuw-Saint-Pierre", -"Lège-Cap-Ferret", -"Légéville-et-Bonfays", -"Légion-d'Honneur", -"Léguillac-de-Cercles", -"Léguillac-de-l'Auche", -"légume-feuille", -"légume-fleur", -"légume-fruit", -"légume-racine", -"légumes-feuilles", -"légumes-fleurs", -"légumes-fruits", -"légumes-racines", -"légumes-tiges", -"légume-tige", "Leidschendam-Voorburg", -"Leigné-les-Bois", "Leignes-sur-Fontaine", +"Leigné-les-Bois", "Leigné-sur-Usseau", "Leinefelde-Worbis", "Leinfelden-Echterdingen", "Leintz-Gatzaga", "Lelin-Lapujolle", -"Leménil-Mitry", -"lemmer-geyer", -"lemmer-geyers", "Lempdes-sur-Allagnon", "Lempire-aux-Bois", +"Leménil-Mitry", "Lens-Lestang", "Lens-Saint-Remy", "Lens-Saint-Servais", "Lens-sur-Geer", -"Lentillac-du-Causse", "Lentillac-Lauzès", "Lentillac-Saint-Blaise", -"léopard-garou", -"léopards-garous", +"Lentillac-du-Causse", "Leo-Stichting", -"Lépanges-sur-Vologne", -"Lépin-le-Lac", -"lépisostée-alligator", -"Lépron-les-Vallées", -"lepto-kurticité", -"lepto-kurticités", -"lepto-kurtique", -"lepto-kurtiques", "Lepuix-Neuf", "Lerm-et-Musset", -"Leschères-sur-le-Blaiseron", +"Les Adrets-de-l'Estérel", +"Les Aix-d'Angillon", +"Les Alluets-le-Roi", +"Les Ancizes-Comps", +"Les Angles-sur-Corrèze", +"Les Anses-d'Arlet", +"Les Artigues-de-Lussac", +"Les Autels-Villevillon", +"Les Authieux-Papion", +"Les Authieux-du-Puits", +"Les Authieux-sur-Calonne", +"Les Authieux-sur-le-Port-Saint-Ouen", +"Les Avanchers-Valmorel", +"Les Avenières Veyrins-Thuellin", +"Les Baux-Sainte-Croix", +"Les Baux-de-Breteuil", +"Les Baux-de-Provence", +"Les Bois d'Anjou", +"Les Bordes-Aumont", +"Les Bordes-sur-Arize", +"Les Bordes-sur-Lez", +"Les Cent-Acres", +"Les Champs-Géraux", +"Les Champs-de-Losque", +"Les Chapelles-Bourbon", +"Les Chavannes-en-Maurienne", +"Les Châtelliers-Notre-Dame", +"Les Clayes-sous-Bois", +"Les Contamines-Montjoie", +"Les Corvées-les-Yys", +"Les Costes-Gozon", +"Les Côtes-d'Arey", +"Les Côtes-de-Corps", +"Les Deux-Fays", +"Les Deux-Villes", +"Les Essards-Taignevaux", +"Les Essarts-le-Roi", +"Les Essarts-le-Vicomte", +"Les Essarts-lès-Sézanne", +"Les Eyzies-de-Tayac-Sireuil", +"Les Grandes-Armoises", +"Les Grandes-Chapelles", +"Les Grandes-Loges", +"Les Grandes-Ventes", +"Les Grands-Chézeaux", +"Les Granges-Gontardes", +"Les Granges-le-Roi", +"Les Hautes-Rivières", +"Les Hauts-de-Chée", +"Les Hôpitaux-Neufs", +"Les Hôpitaux-Vieux", +"Les Isles-Bardel", +"Les Istres-et-Bury", +"Les Landes-Genusson", +"Les Loges-Marchis", +"Les Loges-Margueron", +"Les Loges-Saulces", +"Les Loges-en-Josas", +"Les Loges-sur-Brécey", +"Les Lucs-sur-Boulogne", +"Les Lèves-et-Thoumeyragues", +"Les Magnils-Reigniers", +"Les Martres-d'Artière", +"Les Martres-de-Veyre", +"Les Moitiers-d'Allonne", +"Les Moitiers-en-Bauptois", +"Les Monts d'Andaine", +"Les Monts-Verts", +"Les Moutiers-en-Auge", +"Les Moutiers-en-Cinglais", +"Les Moutiers-en-Retz", +"Les Noës-près-Troyes", +"Les Ollières-sur-Eyrieux", +"Les Ormes-sur-Voulzie", +"Les Pavillons-sous-Bois", +"Les Pennes-Mirabeau", +"Les Petites-Armoises", +"Les Petites-Loges", +"Les Plains-et-Grands-Essarts", +"Les Planches-en-Montagne", +"Les Planches-près-Arbois", +"Les Ponts-de-Cé", +"Les Portes-en-Ré", +"Les Quatre-Routes-du-Lot", +"Les Rivières-Henruel", +"Les Roches-de-Condrieu", +"Les Roches-l'Évêque", +"Les Rosiers-sur-Loire", +"Les Rouges-Eaux", +"Les Rues-des-Vignes", +"Les Sables-d'Olonne", +"Les Salles-Lavauguyon", +"Les Salles-de-Castillon", +"Les Salles-du-Gardon", +"Les Salles-sur-Verdon", +"Les Souhesmes-Rampont", +"Les Terres-de-Chaux", +"Les Thilliers-en-Vexin", +"Les Touches-de-Périgny", +"Les Trois-Bassins", +"Les Trois-Domaines", +"Les Trois-Moutiers", +"Les Trois-Pierres", +"Les Trois-Îlets", +"Les Ventes-de-Bourse", +"Les Verchers-sur-Layon", +"Les Villards-sur-Thônes", +"Les Églises-d'Argenteuil", +"Les Églisottes-et-Chalaures", "Lesches-en-Diois", +"Leschères-sur-le-Blaiseron", "Lescouët-Gouarec", "Lescouët-Jugon", -"Lescure-d'Albigeois", "Lescure-Jaoul", -"lèse-majesté", -"lèse-majestés", -"Lésignac-Durand", +"Lescure-d'Albigeois", "Lesparre-Médoc", "Lespielle-Germenaud-Lannegrasse", "Lesquielles-Saint-Germain", @@ -14083,135 +7553,64 @@ FR_BASE_EXCEPTIONS = [ "Leuville-sur-Orge", "Leuze-en-Hainaut", "Leval-Chaudeville", -"Levallois-Perret", "Leval-Trahegnies", -"lève-cul", -"lève-culs", -"lève-gazon", -"lève-glace", -"lève-glaces", -"lever-dieu", +"Levallois-Perret", "Levesville-la-Chenard", -"lève-tard", -"lève-tôt", -"lève-vitre", -"lève-vitres", -"Lévignac-de-Guyenne", -"Lévis-Saint-Nom", -"lévi-straussien", -"lévi-straussienne", -"lévi-straussiennes", -"lévi-straussiens", -"Lévy-Saint-Nom", "Leyritz-Moncassin", -"Lézat-sur-Lèze", "Lez-Fontaine", -"Lézignan-Corbières", -"Lézignan-la-Cèbe", -"L-flampropisopropyl", -"lgbti-friendly", -"LGBTI-friendly", -"lgbti-phobie", -"LGBTI-phobie", -"lgbti-phobies", -"LGBTI-phobies", -"L-glycéraldéhyde", +"Li-Fi", "Liancourt-Fosse", "Liancourt-Saint-Pierre", -"liane-corail", -"lianes-corail", "Lias-d'Armagnac", -"libéral-conservateur", -"libéral-conservatisme", -"liberum-veto", -"libidino-calotin", "Libramont-Chevigny", -"libre-choix", -"libre-échange", -"libre-échangisme", -"libre-échangismes", -"libre-échangiste", -"libre-échangistes", -"libre-penseur", -"libre-penseuse", -"libres-choix", -"libre-service", -"libres-penseurs", -"libres-penseuses", -"libres-services", "Libre-Ville", -"libyco-berbère", -"libyco-berbères", -"lice-po", "Licey-sur-Vingeanne", "Lichans-Sunhar", -"liche-casse", +"Lichterfeld-Schacksdorf", "Lichères-près-Aigremont", "Lichères-sur-Yonne", -"Lichterfeld-Schacksdorf", -"licol-drisse", -"licols-drisses", "Licq-Athérey", "Licy-Clignon", -"lie-de-vin", -"Lierde-Sainte-Marie", "Lierde-Saint-Martin", +"Lierde-Sainte-Marie", "Liesse-Notre-Dame", "Liesville-sur-Douve", -"lieu-dit", +"Lieu-Saint-Amand", +"Lieu-Saint-Amandinois", +"Lieu-Saint-Amandinoise", +"Lieu-Saint-Amandinoises", "Lieuran-Cabrières", "Lieuran-lès-Béziers", -"Lieu-Saint-Amand", -"lieu-saint-amandinois", -"Lieu-Saint-Amandinois", -"lieu-saint-amandinoise", -"Lieu-Saint-Amandinoise", -"lieu-saint-amandinoises", -"Lieu-Saint-Amandinoises", -"lieutenant-colonel", -"lieutenant-général", -"lieutenant-gouverneur", -"lieutenants-colonels", -"lieux-dits", "Liffol-le-Grand", "Liffol-le-Petit", -"Li-Fi", "Lignan-de-Bazas", "Lignan-de-Bordeaux", "Lignan-sur-Orb", -"ligne-de-foulée", -"lignes-de-foulée", "Lignières-Châtelain", +"Lignières-Orgères", +"Lignières-Sonneville", "Lignières-de-Touraine", "Lignières-en-Vimeu", "Lignières-la-Carelle", -"Lignières-Orgères", -"Lignières-Sonneville", "Lignières-sur-Aire", "Lignol-le-Château", +"Ligny-Haucourt", +"Ligny-Saint-Flochel", +"Ligny-Thilloy", "Ligny-en-Barrois", "Ligny-en-Brionnais", "Ligny-en-Cambrésis", -"Ligny-Haucourt", "Ligny-le-Châtel", "Ligny-le-Ribault", "Ligny-lès-Aire", -"Ligny-Saint-Flochel", "Ligny-sur-Canche", -"Ligny-Thilloy", "Lille-sous-Mauréal", "Lille-sous-Montréal", "Lillois-Witterzée", -"limande-sole", -"limande-soles", -"limandes-soles", "Limbach-Oberfrohna", "Limburg-Weilburg", -"lime-bois", "Limeil-Brévannes", "Limetz-Villez", -"lime-uranite", -"lime-uranites", "Limey-Remenauville", "Limoges-Fourches", "Limogne-en-Quercy", @@ -14222,20 +7621,15 @@ FR_BASE_EXCEPTIONS = [ "Lindre-Haute", "Linières-Bouton", "Linkenheim-Hochstetten", -"linon-batiste", -"linon-batistes", "Lintot-les-Bois", "Liny-devant-Dun", "Lion-devant-Dun", "Lion-en-Beauce", "Lion-en-Sullias", -"lion-garou", -"lions-garous", "Lion-sur-Mer", "Liorac-sur-Louyre", "Lioux-les-Monges", "Lippersdorf-Erdmannsdorf", -"lire-écrire", "Lisle-en-Barrois", "Lisle-en-Rigault", "Lisle-sur-Tarn", @@ -14243,237 +7637,78 @@ FR_BASE_EXCEPTIONS = [ "Lissac-sur-Couze", "Lissay-Lochy", "Lisse-en-Champagne", -"Listrac-de-Durèze", "Listrac-Médoc", -"lit-cage", -"lit-clos", +"Listrac-de-Durèze", "Lit-et-Mixe", -"litho-typographia", -"litho-typographiai", -"litho-typographiaient", -"litho-typographiais", -"litho-typographiait", -"litho-typographiâmes", -"litho-typographiant", -"litho-typographias", -"litho-typographiasse", -"litho-typographiassent", -"litho-typographiasses", -"litho-typographiassiez", -"litho-typographiassions", -"litho-typographiât", -"litho-typographiâtes", -"litho-typographie", -"litho-typographié", -"litho-typographiée", -"litho-typographiées", -"litho-typographient", -"litho-typographier", -"litho-typographiera", -"litho-typographierai", -"litho-typographieraient", -"litho-typographierais", -"litho-typographierait", -"litho-typographieras", -"litho-typographièrent", -"litho-typographierez", -"litho-typographieriez", -"litho-typographierions", -"litho-typographierons", -"litho-typographieront", -"litho-typographies", -"litho-typographiés", -"litho-typographiez", -"litho-typographiiez", -"litho-typographiions", -"litho-typographions", -"lits-cages", -"lits-clos", -"little-endian", "Livarot-Pays-d'Auge", "Liverdy-en-Brie", "Livers-Cazelles", "Livet-en-Saosnois", "Livet-et-Gavet", "Livet-sur-Authou", -"living-room", -"living-rooms", "Livinhac-le-Haut", -"Livré-la-Touche", -"livres-cassettes", -"Livré-sur-Changeon", -"livret-police", "Livron-sur-Drôme", "Livry-Gargan", "Livry-Louvercy", "Livry-sur-Seine", +"Livré-la-Touche", +"Livré-sur-Changeon", "Lixing-lès-Rouhling", "Lixing-lès-Saint-Avold", "Lizy-sur-Ourcq", -"localité-type", -"location-financement", +"Lo-Reninge", "Loc-Brévalaire", "Loc-Eguiner", -"Loc-Éguiner", "Loc-Eguiner-Saint-Thégonnec", -"Loc-Éguiner-Saint-Thégonnec", "Loc-Envel", +"Loc-Éguiner", +"Loc-Éguiner-Saint-Thégonnec", "Loches-sur-Ource", "Loché-sur-Indrois", -"lock-out", -"lock-outa", -"lock-outai", -"lock-outaient", -"lock-outais", -"lock-outait", -"lock-outâmes", -"lock-outant", -"lock-outas", -"lock-outasse", -"lock-outassent", -"lock-outasses", -"lock-outassiez", -"lock-outassions", -"lock-outât", -"lock-outâtes", -"lock-oute", -"lock-outé", -"lock-outée", -"lock-outées", -"lock-outent", -"lock-outer", -"lock-outera", -"lock-outerai", -"lock-outeraient", -"lock-outerais", -"lock-outerait", -"lock-outeras", -"lock-outèrent", -"lock-outerez", -"lock-outeriez", -"lock-outerions", -"lock-outerons", -"lock-outeront", -"lock-outes", -"lock-outés", -"lock-outez", -"lock-outiez", -"lock-outions", -"lock-outons", -"lock-outs", "Locmaria-Berrien", "Locmaria-Grand-Champ", "Locmaria-Plouzané", "Locoal-Mendon", -"locoalo-mendonnais", "Locoalo-Mendonnais", -"locoalo-mendonnaise", "Locoalo-Mendonnaise", -"locoalo-mendonnaises", "Locoalo-Mendonnaises", -"locution-phrase", -"locutions-phrases", -"Loèche-les-Bains", -"Loèche-Ville", -"loemmer-geyer", -"lœmmer-geyer", -"loemmer-geyers", -"lœmmer-geyers", "Loenen-Kronenburg", -"logan-berry", -"logan-berrys", "Loge-Fougereuse", -"logiciel-socle", "Logny-Bogny", "Logny-lès-Aubenton", "Logny-lès-Chaumont", "Logonna-Daoulas", "Logonna-Quimerch", -"logo-syllabique", -"logo-syllabiques", -"Logrian-et-Comiac-de-Florian", "Logrian-Florian", -"Loguivy-lès-Lannion", +"Logrian-et-Comiac-de-Florian", "Loguivy-Plougras", +"Loguivy-lès-Lannion", "Lohe-Föhrden", "Lohe-Rickelshof", "Lohitzun-Oyhercq", "Lohn-Ammannsegg", -"loi-cadre", -"loi-écran", -"Loigné-sur-Mayenne", "Loigny-la-Bataille", -"loi-programme", +"Loigné-sur-Mayenne", +"Loir-et-Cher", "Loire-Atlantique", "Loire-Authion", "Loire-Inférieure", "Loire-les-Marais", -"Loiré-sur-Nie", "Loire-sur-Rhône", -"Loir-et-Cher", "Loiron-Ruillé", -"lois-cadre", -"lois-écrans", +"Loiré-sur-Nie", "Loisey-Culey", "Loison-sous-Lens", "Loison-sur-Créquoise", -"lois-programme", "Loisy-en-Brie", "Loisy-sur-Marne", "Loitsche-Heinrichsberg", "Lombeek-Notre-Dame", -"lombo-costal", -"lombo-costo-trachélien", -"lombo-dorso-trachélien", -"lombo-huméral", -"lombo-sacré", -"lombri-composta", -"lombri-compostai", -"lombri-compostaient", -"lombri-compostais", -"lombri-compostait", -"lombri-compostâmes", -"lombri-compostant", -"lombri-compostas", -"lombri-compostasse", -"lombri-compostassent", -"lombri-compostasses", -"lombri-compostassiez", -"lombri-compostassions", -"lombri-compostât", -"lombri-compostâtes", -"lombri-composte", -"lombri-composté", -"lombri-compostée", -"lombri-compostées", -"lombri-compostent", -"lombri-composter", -"lombri-compostera", -"lombri-composterai", -"lombri-composteraient", -"lombri-composterais", -"lombri-composterait", -"lombri-composteras", -"lombri-compostèrent", -"lombri-composterez", -"lombri-composteriez", -"lombri-composterions", -"lombri-composterons", -"lombri-composteront", -"lombri-compostes", -"lombri-compostés", -"lombri-compostez", -"lombri-compostiez", -"lombri-compostions", -"lombri-compostons", "Lomont-sur-Crête", -"lompénie-serpent", "Lona-Lases", "Longchamp-sous-Châtenois", -"Longchamps-sur-Aire", "Longchamp-sur-Aujon", -"long-courrier", -"long-courriers", +"Longchamps-sur-Aire", "Longeau-Percey", "Longecourt-en-Plaine", "Longecourt-lès-Culêtre", @@ -14482,63 +7717,39 @@ FR_BASE_EXCEPTIONS = [ "Longeville-en-Barrois", "Longeville-lès-Metz", "Longeville-lès-Saint-Avold", -"Longevilles-Mont-d'Or", -"Longeville-sur-la-Laines", "Longeville-sur-Mer", "Longeville-sur-Mogne", -"long-grain", -"long-jointé", -"long-jointée", -"long-métrage", +"Longeville-sur-la-Laines", +"Longevilles-Mont-d'Or", "Longny-au-Perche", "Longny-les-Villages", "Longpont-sur-Orge", -"Longpré-les-Corps-Saints", "Longpré-le-Sec", -"longs-courriers", -"longs-métrages", -"long-temps", -"long-tems", -"longue-épine", +"Longpré-les-Corps-Saints", +"Longue-Rivois", "Longueil-Annel", "Longueil-Sainte-Marie", -"Longué-Jumelles", -"longue-langue", "Longuenée-en-Anjou", -"Longue-Rivois", -"longues-épines", -"longues-langues", "Longues-sur-Mer", -"longues-vues", "Longueval-Barbonval", "Longueville-sur-Aube", "Longueville-sur-Scie", -"longue-vue", -"Longwé-l'Abbaye", +"Longué-Jumelles", "Longwy-sur-le-Doubs", +"Longwé-l'Abbaye", "Lonlay-l'Abbaye", "Lonlay-le-Tesson", "Lons-le-Saunier", "Loon-Plage", "Loos-en-Gohelle", -"loqu'du", -"loqu'due", -"loqu'dues", -"loqu'dus", -"lord-lieutenance", -"lord-lieutenances", -"lord-lieutenant", -"lord-lieutenants", -"lord-maire", -"Lo-Reninge", "Loreto-di-Casinca", "Loreto-di-Tallano", "Loriol-du-Comtat", "Loriol-sur-Drôme", "Lorp-Sentaraille", "Lorrez-le-Bocage-Préaux", -"Lorry-lès-Metz", "Lorry-Mardigny", +"Lorry-lès-Metz", "Loscouët-sur-Meu", "Louan-Villegruis-Fontaine", "Loubens-Lauragais", @@ -14547,108 +7758,72 @@ FR_BASE_EXCEPTIONS = [ "Louette-Saint-Denis", "Louette-Saint-Pierre", "Lougé-sur-Maire", -"louise-bonne", -"louises-bonnes", -"Loulans-les-Forges", "Loulans-Verchamp", -"loup-cerve", -"loup-cervier", -"loup-garou", -"Loupiac-de-la-Réole", +"Loulans-les-Forges", "Loup-Maëlle", +"Loupiac-de-la-Réole", "Louppy-le-Château", "Louppy-sur-Chée", "Louppy-sur-Loison", -"loups-cerves", -"loups-cerviers", -"loups-garous", "Lourdios-Ichère", -"lourd-léger", "Lourdoueix-Saint-Michel", "Lourdoueix-Saint-Pierre", -"lourds-légers", "Loures-Barousse", "Louresse-Rochemenier", "Lourouer-Saint-Laurent", "Louroux-Bourbonnais", +"Louroux-Hodement", "Louroux-de-Beaune", "Louroux-de-Bouble", -"Louroux-Hodement", -"lourouzien-bourbonnais", "Lourouzien-Bourbonnais", -"lourouzienne-bourbonnaise", "Lourouzienne-Bourbonnaise", -"lourouziennes-bourbonnaises", "Lourouziennes-Bourbonnaises", -"lourouziens-bourbonnais", "Lourouziens-Bourbonnais", "Lourties-Monbrun", "Loussous-Débat", "Louvain-la-Neuve", -"louve-garelle", -"louve-garolle", -"louve-garou", "Louvemont-Côte-du-Poivre", -"louves-garelles", -"louves-garolles", -"louves-garous", -"louveteau-garou", -"louveteaux-garous", "Louvie-Juzon", -"Louvières-en-Auge", "Louvie-Soubiron", -"louvie-soubironnais", "Louvie-Soubironnais", -"louvie-soubironnaise", "Louvie-Soubironnaise", -"louvie-soubironnaises", "Louvie-Soubironnaises", -"Louvigné-de-Bais", -"Louvigné-du-Désert", "Louvignies-Bavay", "Louvignies-Quesnoy", +"Louvigné-de-Bais", +"Louvigné-du-Désert", "Louville-la-Chenard", "Louvilliers-en-Drouais", "Louvilliers-lès-Perche", +"Louvières-en-Auge", "Louzac-Saint-André", -"love-in", -"low-cost", -"low-costs", -"low-tech", "Loye-sur-Arnon", "Lozoyuela-Navas-Sieteiglesias", +"Loèche-Ville", +"Loèche-les-Bains", "Lubret-Saint-Luc", "Luby-Betmont", "Luc-Armau", -"Luçay-le-Libre", -"Luçay-le-Mâle", -"Lucbardez-et-Bargues", -"Lucenay-le-Duc", -"Lucenay-lès-Aix", -"Lucenay-l'Evêque", -"Lucenay-l'Évêque", "Luc-en-Diois", -"Lucé-sous-Ballon", -"Luché-Pringé", -"Luché-sur-Brioux", -"Luché-Thouarsais", -"Lüchow-Dannenberg", "Luc-la-Primaube", -"Lucq-de-Béarn", "Luc-sur-Aude", "Luc-sur-Mer", "Luc-sur-Orbieu", +"Lucbardez-et-Bargues", +"Lucenay-l'Evêque", +"Lucenay-l'Évêque", +"Lucenay-le-Duc", +"Lucenay-lès-Aix", +"Luché-Pringé", +"Luché-Thouarsais", +"Luché-sur-Brioux", +"Lucq-de-Béarn", "Lucy-le-Bocage", "Lucy-le-Bois", "Lucy-sur-Cure", "Lucy-sur-Yonne", -"ludo-éducatif", +"Lucé-sous-Ballon", "Ludon-Médoc", -"ludo-sportif", -"ludo-sportifs", -"ludo-sportive", -"ludo-sportives", -"Lué-en-Baugeois", "Lugaut-Retjons", "Lugny-Bourbonnais", "Lugny-Champagne", @@ -14656,27 +7831,22 @@ FR_BASE_EXCEPTIONS = [ "Lugo-di-Nazza", "Lugon-et-l'Île-du-Carnay", "Luhe-Wildenau", -"lui-même", -"lumen-seconde", -"lumens-secondes", -"Luméville-en-Ornois", "Lumigny-Nesles-Ormeaux", +"Luméville-en-Ornois", "Lunel-Viel", -"luni-solaire", -"luni-solaires", "Lunow-Stolzenhagen", "Lupiñén-Ortilla", "Luppé-Violles", "Lurbe-Saint-Christau", -"Lurcy-le-Bourg", "Lurcy-Lévis", "Lurcy-Lévy", +"Lurcy-le-Bourg", "Lury-sur-Arnon", +"Lus-la-Croix-Haute", "Lusignan-Grand", "Lusignan-Petit", "Lusigny-sur-Barse", "Lusigny-sur-Ouche", -"Lus-la-Croix-Haute", "Lussac-les-Châteaux", "Lussac-les-Eglises", "Lussac-les-Églises", @@ -14686,365 +7856,181 @@ FR_BASE_EXCEPTIONS = [ "Lussault-sur-Loire", "Lussery-Villars", "Lussy-sur-Morges", -"Lüterkofen-Ichertswil", -"Lüterswil-Gächliwil", "Luthenay-Uxeloup", -"Łutselk'e", "Luttenbach-près-Munster", -"Lüttow-Valluhn", "Lutz-en-Dunois", -"Luxémont-et-Villotte", "Luxe-Sumberraute", "Luxeuil-les-Bains", +"Luxémont-et-Villotte", "Luz-Saint-Sauveur", "Luzy-Saint-Martin", "Luzy-sur-Marne", +"Luçay-le-Libre", +"Luçay-le-Mâle", +"Lué-en-Baugeois", "Ly-Fontaine", "Lyons-la-Forêt", -"lyro-guitare", "Lys-Haut-Layon", -"Lys-lez-Lannoy", "Lys-Saint-Georges", +"Lys-lez-Lannoy", +"Lâ-Todin", +"Lège-Cap-Ferret", +"Léa-Lisa", +"Lédas-et-Penthiès", +"Lées-Athas", +"Légion-d'Honneur", +"Léguillac-de-Cercles", +"Léguillac-de-l'Auche", +"Légéville-et-Bonfays", +"Lépanges-sur-Vologne", +"Lépin-le-Lac", +"Lépron-les-Vallées", +"Lésignac-Durand", +"Lévignac-de-Guyenne", +"Lévis-Saint-Nom", +"Lévy-Saint-Nom", +"Lézat-sur-Lèze", +"Lézignan-Corbières", +"Lézignan-la-Cèbe", +"Lüchow-Dannenberg", +"Lüterkofen-Ichertswil", +"Lüterswil-Gächliwil", +"Lüttow-Valluhn", +"M'Tsangamouji", "Maarke-Kerkem", "Maast-et-Violaine", -"mac-adamisa", -"mac-adamisai", -"mac-adamisaient", -"mac-adamisais", -"mac-adamisait", -"mac-adamisâmes", -"mac-adamisant", -"mac-adamisas", -"mac-adamisasse", -"mac-adamisassent", -"mac-adamisasses", -"mac-adamisassiez", -"mac-adamisassions", -"mac-adamisât", -"mac-adamisâtes", -"mac-adamise", -"mac-adamisé", -"mac-adamisée", -"mac-adamisées", -"mac-adamisent", -"mac-adamiser", -"mac-adamisera", -"mac-adamiserai", -"mac-adamiseraient", -"mac-adamiserais", -"mac-adamiserait", -"mac-adamiseras", -"mac-adamisèrent", -"mac-adamiserez", -"mac-adamiseriez", -"mac-adamiserions", -"mac-adamiserons", -"mac-adamiseront", -"mac-adamises", -"mac-adamisés", -"mac-adamisez", -"mac-adamisiez", -"mac-adamisions", -"mac-adamisons", +"Machecoul-Saint-Même", "Macédoine-Centrale", "Macédoine-Occidentale", "Macédoine-Orientale-et-Thrace", -"mac-ferlane", -"mac-ferlanes", -"mâche-bouchons", -"Machecoul-Saint-Même", -"mâche-dru", -"mâche-laurier", -"machin-chose", -"machin-choses", -"machin-chouette", -"machine-outil", -"machines-outils", -"machins-chouettes", -"machon-gorgeon", -"mac-kintosh", -"mac-kintoshs", -"Mâcot-la-Plagne", -"ma'di", "Madlitz-Wilmersdorf", "Madonne-et-Lamerey", -"maël-carhaisien", -"Maël-Carhaisien", -"maël-carhaisienne", -"Maël-Carhaisienne", -"maël-carhaisiennes", -"Maël-Carhaisiennes", -"maël-carhaisiens", -"Maël-Carhaisiens", -"Maël-Carhaix", -"Maël-Pestivien", -"Maen-Roch", "Mae-West", "Mae-Wests", -"magasin-pilote", -"magasins-pilotes", +"Maen-Roch", "Magnac-Bourg", "Magnac-Laval", "Magnac-Lavalette-Villars", "Magnac-sur-Touvre", "Magnat-l'Etrange", "Magnat-l'Étrange", -"magnésio-anthophyllite", -"magnésio-anthophyllites", -"magnésio-axinite", -"magnésio-axinites", -"magnésio-calcite", -"magnésio-calcites", -"magnéto-électrique", -"magnéto-électriques", -"magnéto-optique", -"magnéto-optiques", "Magneux-Haute-Rive", "Magnicourt-en-Comte", "Magnicourt-sur-Canche", "Magny-Châtelard", "Magny-Cours", "Magny-Danigon", -"Magny-en-Bessin", -"Magny-en-Vexin", "Magny-Fouchard", "Magny-Jobert", +"Magny-Lambert", +"Magny-Lormes", +"Magny-Montarlot", +"Magny-Saint-Médard", +"Magny-Vernois", +"Magny-en-Bessin", +"Magny-en-Vexin", "Magny-la-Campagne", "Magny-la-Fosse", -"Magny-Lambert", "Magny-la-Ville", "Magny-le-Désert", "Magny-le-Freule", "Magny-le-Hongre", -"Magny-lès-Aubigny", "Magny-les-Hameaux", +"Magny-lès-Aubigny", "Magny-lès-Jussey", "Magny-lès-Villers", -"Magny-Lormes", -"Magny-Montarlot", -"Magny-Saint-Médard", "Magny-sur-Tille", -"Magny-Vernois", "Magstatt-le-Bas", "Magstatt-le-Haut", -"mahi-mahi", -"mah-jong", -"mah-jongs", "Maignaut-Tauzia", "Maignelay-Montigny", -"mail-coach", "Mailhac-sur-Benaize", "Mailleroncourt-Charette", "Mailleroncourt-Saint-Pancras", "Mailley-et-Chazelot", -"mailly-castellois", "Mailly-Castellois", -"mailly-castelloise", "Mailly-Castelloise", -"mailly-castelloises", "Mailly-Castelloises", "Mailly-Champagne", +"Mailly-Maillet", +"Mailly-Raineval", "Mailly-la-Ville", "Mailly-le-Camp", "Mailly-le-Château", -"Mailly-Maillet", -"Mailly-Raineval", "Mailly-sur-Seille", -"main-brune", -"main-courante", -"Maincourt-sur-Yvette", -"main-d'oeuvre", -"main-d'œuvre", -"maine-anjou", -"Maine-de-Boixe", -"Maine-et-Loire", -"main-forte", "Main-Kinzig", -"main-militaire", -"mains-courantes", -"mains-d'oeuvre", -"mains-d'œuvre", "Main-Spessart", "Main-Tauber", "Main-Taunus", -"maire-adjoint", -"Mairé-Levescault", -"maires-adjoints", +"Maincourt-sur-Yvette", +"Maine-de-Boixe", +"Maine-et-Loire", "Mairy-Mainville", "Mairy-sur-Marne", +"Mairé-Levescault", "Maisdon-sur-Sèvre", "Maisey-le-Duc", "Maisières-Notre-Dame", "Maisnil-lès-Ruitz", "Maison-Blanche", -"Maisoncelle-et-Villers", +"Maison-Feyne", +"Maison-Maugis", +"Maison-Ponthieu", +"Maison-Roland", +"Maison-Rouge", +"Maison-des-Champs", "Maisoncelle-Saint-Pierre", +"Maisoncelle-Tuilerie", +"Maisoncelle-et-Villers", +"Maisoncelles-Pelvey", "Maisoncelles-du-Maine", "Maisoncelles-en-Brie", "Maisoncelles-en-Gâtinais", "Maisoncelles-la-Jourdan", -"Maisoncelles-Pelvey", "Maisoncelles-sur-Ajon", -"Maisoncelle-Tuilerie", -"Maison-des-Champs", -"Maison-Feyne", -"Maison-Maugis", -"maison-mère", "Maisonnais-sur-Tardoire", -"Maison-Ponthieu", -"Maison-Roland", -"Maison-Rouge", "Maisons-Alfort", +"Maisons-Laffitte", "Maisons-du-Bois-Lièvremont", "Maisons-en-Champagne", -"Maisons-Laffitte", "Maisons-lès-Chaource", "Maisons-lès-Soulaines", -"maisons-mères", -"maître-assistant", -"maitre-autel", -"maître-autel", -"maître-bau", -"maitre-chanteur", -"maître-chanteur", -"maître-chanteuse", -"maitre-chien", -"maître-chien", -"maître-cylindre", -"maître-jacques", -"maître-mot", -"maitre-nageur", -"maître-nageur", -"maitre-nageuse", -"maître-nageuse", -"maîtres-assistants", -"maîtres-autels", -"maîtres-chanteurs", -"maîtres-chanteuses", -"maitres-chiens", -"maîtres-chiens", -"maîtres-cylindres", -"maîtres-jacques", -"maîtres-mots", -"maitres-nageurs", -"maîtres-nageurs", -"maitres-nageuses", -"maîtres-nageuses", -"maîtresse-femme", -"maitresse-nageuse", -"maîtresse-nageuse", -"maîtresses-femmes", -"maitresses-nageuses", -"maîtresses-nageuses", "Maizières-la-Grande-Paroisse", "Maizières-lès-Brienne", "Maizières-lès-Metz", "Maizières-lès-Vic", "Maizières-sur-Amance", -"ma-jong", -"ma-jongs", -"make-up", -"make-ups", -"making-of", -"makura-e", -"makura-es", -"mal-aimé", -"mal-aimée", -"mal-aimés", +"Mal-Peigné", +"Mal-Peignée", "Malaincourt-sur-Meuse", "Malancourt-la-Montagne", "Malarce-sur-la-Thines", "Malaucourt-sur-Seille", "Malay-le-Grand", "Malay-le-Petit", -"malayo-polynésien", -"malayo-polynésienne", -"malayo-polynésiennes", -"malayo-polynésiens", "Malayo-Polynésiens", -"mal-baisé", -"mal-baisée", -"mal-baisées", -"mal-baisés", "Malborghetto-Valbruna", -"mal-comprenant", -"mal-comprenants", -"malécite-passamaquoddy", -"mal-égal", "Malemort-du-Comtat", "Malemort-sur-Corrèze", -"mal-en-point", -"mâles-stériles", -"mâle-stérile", -"mâle-stériles", -"mal-être", -"mal-êtres", -"Malèves-Sainte-Marie-Wastines", -"malgré-nous", "Malherbe-sur-Ajon", "Malicorne-sur-Sarthe", "Malines-sur-Meuse", -"mal-information", -"mal-informations", -"mal-jugé", -"mal-jugés", "Mallefougasse-Augès", -"malle-poste", "Malleret-Boussac", "Mallersdorf-Pfaffenberg", "Malleval-en-Vercors", "Malleville-les-Grès", "Malleville-sur-le-Bec", -"mal-logement", -"mal-logements", "Malo-les-Bains", "Malons-et-Elze", -"mal-peigné", -"Mal-Peigné", -"mal-peignée", -"Mal-Peignée", -"mal-pensans", -"mal-pensant", -"mal-pensante", -"mal-pensantes", -"mal-pensants", -"Malsburg-Marzell", -"mals-peignées", "Mals-Peignées", -"mals-peignés", "Mals-Peignés", -"mal-venant", -"mal-venants", +"Malsburg-Marzell", "Malves-en-Minervois", -"mal-voyant", -"mal-voyants", -"m'amie", -"mamie-boomeuse", -"mamie-boomeuses", -"mam'selle", -"mam'selles", -"mamy-boomeuse", -"mamy-boomeuses", -"mam'zelle", -"mam'zelles", +"Malèves-Sainte-Marie-Wastines", "Manas-Bastanous", -"man-bun", -"man-buns", "Mancenans-Lizerne", -"manche-à-balle", -"manche-à-balles", -"manco-liste", -"manco-listes", "Mandailles-Saint-Julien", -"mandant-dépendant", -"mandat-carte", -"mandat-cash", -"mandat-lettre", -"mandat-poste", -"mandats-cartes", -"mandats-cash", -"mandats-lettres", -"mandats-poste", "Mandelieu-la-Napoule", "Mandeville-en-Bessin", "Mandres-aux-Quatre-Tours", @@ -15053,31 +8039,14 @@ FR_BASE_EXCEPTIONS = [ "Mandres-les-Roses", "Mandres-sur-Vair", "Manent-Montané", -"manganico-potassique", -"mangano-ankérite", -"mangano-ankérites", -"mangano-phlogopite", -"mangano-phlogopites", -"manganoso-ammonique", -"mange-Canayen", -"mange-debout", -"mange-disque", -"mange-disques", -"mange-merde", -"mange-piles", -"mange-tout", "Mango-Rosa", -"maniaco-dépressif", -"maniaco-dépressifs", -"maniaco-dépressive", -"maniaco-dépressives", "Maninghen-Henne", "Manneken-pis", -"Manneville-ès-Plains", "Manneville-la-Goupil", "Manneville-la-Pipard", "Manneville-la-Raoult", "Manneville-sur-Risle", +"Manneville-ès-Plains", "Mannweiler-Cölln", "Manoncourt-en-Vermois", "Manoncourt-en-Woëvre", @@ -15089,8 +8058,6 @@ FR_BASE_EXCEPTIONS = [ "Mantes-la-Jolie", "Mantes-la-Ville", "Manzac-sur-Vern", -"mappe-monde", -"mappes-mondes", "Marainville-sur-Madon", "Marais-Vernier", "Marange-Silvange", @@ -15098,33 +8065,28 @@ FR_BASE_EXCEPTIONS = [ "Marat-sur-Aisne", "Maraye-en-Othe", "Marbourg-Biedenkopf", +"Marc-la-Tour", "Marcellaz-Albanais", -"Marcé-sur-Esves", "Marcey-les-Grèves", "Marchais-Beton", "Marchais-Béton", "Marchais-en-Brie", -"Marché-Allouarde", "Marche-en-Famenne", -"marché-gare", -"marché-gares", "Marche-les-Dames", "Marche-lez-Écaussinnes", -"marche-palier", -"Marchéville-en-Woëvre", "Marchienne-au-Pont", "Marchiennes-Campagne", +"Marché-Allouarde", +"Marchéville-en-Woëvre", "Marcigny-sous-Thil", "Marcilhac-sur-Célé", -"Marcillac-la-Croisille", -"Marcillac-la-Croze", "Marcillac-Lanville", "Marcillac-Saint-Quentin", "Marcillac-Vallon", +"Marcillac-la-Croisille", +"Marcillac-la-Croze", "Marcillat-en-Combraille", -"Marcillé-la-Ville", -"Marcillé-Raoul", -"Marcillé-Robert", +"Marcilly-Ogny", "Marcilly-d'Azergues", "Marcilly-en-Bassigny", "Marcilly-en-Beauce", @@ -15138,33 +8100,29 @@ FR_BASE_EXCEPTIONS = [ "Marcilly-le-Pavé", "Marcilly-lès-Buxy", "Marcilly-lès-Vitteaux", -"Marcilly-Ogny", "Marcilly-sur-Eure", "Marcilly-sur-Maulne", "Marcilly-sur-Seine", "Marcilly-sur-Tille", "Marcilly-sur-Vienne", -"Marc-la-Tour", +"Marcillé-Raoul", +"Marcillé-Robert", +"Marcillé-la-Ville", "Marcols-les-Eaux", -"marco-lucanien", -"marco-lucanienne", -"marco-lucaniennes", -"marco-lucaniens", +"Marcq-en-Barœul", "Marcq-en-Barœul", "Marcq-en-Ostrevent", "Marcq-et-Chevières", "Marcy-l'Etoile", "Marcy-l'Étoile", "Marcy-sous-Marle", +"Marcé-sur-Esves", "Mareau-aux-Bois", "Mareau-aux-Prés", -"maréchal-ferrant", -"maréchaux-ferrans", -"maréchaux-ferrants", +"Mareil-Marly", "Mareil-en-Champagne", "Mareil-en-France", "Mareil-le-Guyon", -"Mareil-Marly", "Mareil-sur-Loir", "Mareil-sur-Mauldre", "Maren-Kessel", @@ -15185,98 +8143,71 @@ FR_BASE_EXCEPTIONS = [ "Mareuil-sur-Ourcq", "Marey-lès-Fussey", "Marey-sur-Tille", -"margarino-sulfurique", "Margaux-Cantenac", "Margerie-Chantagret", "Margerie-Hancourt", -"margis-chef", -"margis-chefs", "Margny-aux-Cerises", "Margny-lès-Compiègne", "Margny-sur-Matz", "Margouët-Meymes", -"mariage-sacrement", "Maria-Hoop", "Marie-Ange", "Marie-Antoinette", -"Marie-blanque", -"marie-chantal", "Marie-Chantal", -"marie-chantalerie", -"marie-chantaleries", "Marie-Christine", "Marie-Claire", "Marie-Claude", -"marie-couche-toi-là", -"Marie-couche-toi-là", "Marie-Crochet", -"Marie-Élise", -"Marie-Ève", "Marie-France", "Marie-Françoise", -"marie-galante", "Marie-Galante", -"marie-galantes", "Marie-Gisèle", "Marie-Hélène", -"marie-jeanne", -"marie-jeannes", "Marie-José", "Marie-Laure", -"marie-louise", "Marie-Louise", -"marie-louises", "Marie-Madeleine", "Marie-Marc", -"marie-monastérien", "Marie-Monastérien", -"marie-monastérienne", "Marie-Monastérienne", -"marie-monastériennes", "Marie-Monastériennes", -"marie-monastériens", "Marie-Monastériens", -"marie-montois", "Marie-Montois", -"marie-montoise", "Marie-Montoise", -"marie-montoises", "Marie-Montoises", "Marie-Noëlle", "Marie-Paule", "Marie-Pier", "Marie-Pierre", -"marie-salope", -"maries-salopes", "Marie-Thérèse", -"marie-trintigner", -"Marignac-en-Diois", +"Marie-blanque", +"Marie-couche-toi-là", +"Marie-Ève", +"Marie-Élise", +"Marigna-sur-Valouse", "Marignac-Lasclares", "Marignac-Laspeyres", -"Marigna-sur-Valouse", -"Marigné-Laillé", -"Marigné-Peuton", +"Marignac-en-Diois", "Marigny-Brizay", "Marigny-Chemereau", -"Marigny-en-Orxois", -"Marigny-le-Cahouët", -"Marigny-le-Châtel", -"Marigny-l'Eglise", -"Marigny-l'Église", "Marigny-Le-Lozon", -"Marigny-lès-Reullée", -"Marigny-les-Usages", "Marigny-Marmande", "Marigny-Saint-Marcel", +"Marigny-en-Orxois", +"Marigny-l'Eglise", +"Marigny-l'Église", +"Marigny-le-Cahouët", +"Marigny-le-Châtel", +"Marigny-les-Usages", +"Marigny-lès-Reullée", "Marigny-sur-Yonne", +"Marigné-Laillé", +"Marigné-Peuton", "Marillac-le-Franc", "Marimont-lès-Bénestroff", "Maring-Noviand", -"marin-pêcheur", -"marins-pêcheurs", -"Marizy-Sainte-Geneviève", "Marizy-Saint-Mard", -"marka-dafing", +"Marizy-Sainte-Geneviève", "Markina-Xemein", "Marles-en-Brie", "Marles-les-Mines", @@ -15291,64 +8222,21 @@ FR_BASE_EXCEPTIONS = [ "Marnay-sur-Seine", "Marnes-la-Coquette", "Marnhagues-et-Latour", -"marno-bitumineux", -"marno-calcaire", -"marno-calcaires", "Marolles-en-Beauce", "Marolles-en-Brie", "Marolles-en-Hurepoix", -"Marolles-lès-Bailly", "Marolles-les-Braults", "Marolles-les-Buis", +"Marolles-lès-Bailly", "Marolles-lès-Saint-Calais", "Marolles-sous-Lignières", "Marolles-sur-Seine", "Marqueny-au-Vallage", -"marque-ombrelle", -"marque-page", -"marque-pagé", -"marque-pagea", -"marque-pageai", -"marque-pageaient", -"marque-pageais", -"marque-pageait", -"marque-pageâmes", -"marque-pageant", -"marque-pageas", -"marque-pageasse", -"marque-pageassent", -"marque-pageasses", -"marque-pageassiez", -"marque-pageassions", -"marque-pageât", -"marque-pageâtes", -"marque-pagée", -"marque-pagées", -"marque-pagent", -"marque-pageons", -"marque-pager", -"marque-pagera", -"marque-pagerai", -"marque-pageraient", -"marque-pagerais", -"marque-pagerait", -"marque-pageras", -"marque-pagèrent", -"marque-pagerez", -"marque-pageriez", -"marque-pagerions", -"marque-pagerons", -"marque-pageront", -"marque-pages", -"marque-pagés", -"marque-pagez", -"marque-pagiez", -"marque-pagions", -"marque-produit", -"marque-produits", -"marques-ombrelles", "Marquette-en-Ostrevant", "Marquette-lez-Lille", +"Mars-la-Tour", +"Mars-sous-Bourcq", +"Mars-sur-Allier", "Marsac-en-Livradois", "Marsac-sur-Don", "Marsac-sur-l'Isle", @@ -15358,125 +8246,71 @@ FR_BASE_EXCEPTIONS = [ "Marseille-en-Beauvaisis", "Marseille-lès-Aubigny", "Marseilles-lès-Aubigny", -"Mars-la-Tour", "Marson-sur-Barboure", "Marssac-sur-Tarn", -"Mars-sous-Bourcq", -"Mars-sur-Allier", "Martailly-lès-Brancion", "Martainville-Epreville", "Martainville-Épreville", -"marteau-de-mer", -"marteau-pilon", -"marteau-piqueur", -"marteaux-pilons", -"marteaux-piqueurs", -"marte-piquant", -"marte-piquants", "Martignas-sur-Jalle", -"Martigné-Briand", -"Martigné-Ferchaud", -"Martigné-sur-Mayenne", "Martigny-Combe", "Martigny-Courpierre", "Martigny-le-Comte", "Martigny-les-Bains", "Martigny-les-Gerbonvaux", "Martigny-sur-l'Ante", -"martin-bâton", -"Martin-bâton", -"martin-bâtons", -"Martin-bâtons", -"martin-chasseur", -"Martincourt-sur-Meuse", +"Martigné-Briand", +"Martigné-Ferchaud", +"Martigné-sur-Mayenne", "Martin-Eglise", +"Martin-bâton", +"Martin-bâtons", "Martin-Église", -"martin-pêcheur", -"martins-chasseurs", -"martin-sec", -"martin-sire", -"martins-pêcheurs", -"martins-sires", -"martins-sucrés", -"martin-sucré", +"Martincourt-sur-Meuse", "Martouzin-Neuville", +"Martres-Tolosane", "Martres-d'Artières", "Martres-de-Rivière", "Martres-sur-Morge", -"Martres-Tolosane", -"martres-zibelines", -"martre-zibeline", -"Maruéjols-lès-Gardon", "Maruri-Jatabe", +"Maruéjols-lès-Gardon", "Marvaux-Vieux", -"Marville-les-Bois", "Marville-Moutiers-Brûlé", -"marxisme-léninisme", -"marxiste-léniniste", -"marxistes-léninistes", +"Marville-les-Bois", "Mary-sur-Marne", -"m'as", -"masa'il", -"masa'ils", -"Masbaraud-Mérignat", "Mas-Blanc", "Mas-Blanc-des-Alpilles", "Mas-Cabardès", -"Mascaraàs-Haron", -"mas-chélyen", "Mas-Chélyen", -"mas-chélyenne", "Mas-Chélyenne", -"mas-chélyennes", "Mas-Chélyennes", -"mas-chélyens", "Mas-Chélyens", +"Mas-Grenier", +"Mas-Saint-Chély", +"Mas-Saintes-Puelles", +"Mas-Tençois", +"Mas-Tençoise", +"Mas-Tençoises", "Mas-d'Auvignon", +"Mas-d'Orcières", "Mas-de-Londres", "Mas-des-Cours", -"Mas-d'Orcières", +"Masbaraud-Mérignat", +"Mascaraàs-Haron", "Masevaux-Niederbruck", -"Mas-Grenier", "Masnuy-Saint-Jean", "Masnuy-Saint-Pierre", "Maspie-Lalonquère-Juillacq", "Massa-Carrara", "Massac-Séran", -"Mas-Saint-Chély", -"Mas-Saintes-Puelles", "Massen-Niederlausitz", -"masseur-kinésithérapeute", -"masseurs-kinésithérapeutes", -"masseuse-kinésithérapeute", -"masseuses-kinésithérapeutes", "Massignieu-de-Rives", "Massillargues-Attuech", "Massingy-lès-Semur", "Massingy-lès-Vitteaux", -"mass-média", -"mass-médias", -"mas-tençois", -"Mas-Tençois", -"mas-tençoise", -"Mas-Tençoise", -"mas-tençoises", -"Mas-Tençoises", -"m'as-tu-vu", -"m'as-tu-vue", -"m'as-tu-vues", -"m'as-tu-vus", "Matafelon-Granges", "Matagne-la-Grande", "Matagne-la-Petite", -"materno-infantile", -"materno-infantiles", -"mathématico-informatique", -"mathématico-informatiques", "Matignicourt-Goncourt", -"matthéo-lucanien", -"matthéo-lucanienne", -"matthéo-lucaniennes", -"matthéo-lucaniens", "Matton-et-Clémency", "Matzlow-Garwitz", "Maubert-Fontaine", @@ -15484,8 +8318,8 @@ FR_BASE_EXCEPTIONS = [ "Maudétour-en-Vexin", "Mauges-sur-Loire", "Mauléon-Barousse", -"Mauléon-d'Armagnac", "Mauléon-Licharre", +"Mauléon-d'Armagnac", "Maulévrier-Sainte-Gertrude", "Maumusson-Laguian", "Maupertus-sur-Mer", @@ -15494,8 +8328,6 @@ FR_BASE_EXCEPTIONS = [ "Maureilhan-et-Raméjean", "Maureillas-las-Illas", "Maurens-Scopont", -"mauritano-marocain", -"mauritano-sénégalais", "Maurupt-le-Montois", "Maussane-les-Alpilles", "Mauves-sur-Huisne", @@ -15506,100 +8338,55 @@ FR_BASE_EXCEPTIONS = [ "Mauvezin-sur-Gupie", "Mauzac-et-Grand-Castang", "Mauzens-et-Miremont", -"Mauzé-sur-le-Mignon", "Mauzé-Thouarsais", +"Mauzé-sur-le-Mignon", "Mavilly-Mandelot", "Mawashi-geri", "Maxey-sur-Meuse", "Maxey-sur-Vaise", "Maxhütte-Haidhof", -"maxillo-dentaire", -"maxillo-facial", -"maxillo-labial", -"maxillo-musculaire", "Maxilly-sur-Léman", "Maxilly-sur-Saône", -"Mayence-Bingen", -"Mayen-Coblence", "May-en-Multien", +"May-sur-Orne", +"Mayen-Coblence", +"Mayence-Bingen", "Mayres-Savel", "Mayrinhac-Lentour", -"May-sur-Orne", "Mazan-l'Abbaye", -"Mazé-Milon", "Mazerat-Aurouze", -"Mazères-de-Neste", -"Mazères-Lezons", -"Mazères-sur-Salat", "Mazerolles-du-Razès", "Mazerolles-le-Salin", "Mazet-Saint-Voy", "Mazeyrat-Aurouze", "Mazeyrat-d'Allier", +"Mazières-Naresse", "Mazières-de-Touraine", "Mazières-en-Gâtine", "Mazières-en-Mauges", -"Mazières-Naresse", "Mazières-sur-Béronne", +"Mazères-Lezons", +"Mazères-de-Neste", +"Mazères-sur-Salat", +"Mazé-Milon", +"Maël-Carhaisien", +"Maël-Carhaisienne", +"Maël-Carhaisiennes", +"Maël-Carhaisiens", +"Maël-Carhaix", +"Maël-Pestivien", "Mbanza-Ngungu", -"m'bororo", "McDonald's", -"m-commerce", -"m'demma", -"mea-culpa", -"meâ-culpâ", "Meaulne-Vitray", "Meaux-la-Montagne", "Mechelen-aan-de-Maas", -"Mecklembourg-du-Nord-Ouest", "Mecklembourg-Poméranie-Occidentale", "Mecklembourg-Strelitz", -"mécoprop-P", -"médecine-ball", -"médecine-balls", -"médiévale-fantastique", -"médiévales-fantastiques", -"médiéval-fantastique", -"médiévaux-fantastiques", +"Mecklembourg-du-Nord-Ouest", "Medina-Sidonia", -"médio-dorsal", -"médio-européen", -"médio-européenne", -"médio-européennes", -"médio-européens", -"médio-jurassique", -"médio-jurassiques", -"médio-latin", -"médio-latine", -"médio-latines", -"médio-latins", -"médio-océanique", -"médio-océaniques", -"méduse-boite", -"méduse-boîte", -"méduses-boites", -"méduses-boîtes", "Meensel-Kiezegem", "Meerlo-Wanssum", "Meeuwen-Gruitrode", -"méfenpyr-diéthyl", -"méga-ampère", -"méga-ampères", -"méga-église", -"méga-églises", -"méga-électron-volt", -"mégaélectron-volt", -"méga-électron-volts", -"mégaélectron-volts", -"méga-herbivore", -"méga-herbivores", -"mégalo-martyr", -"mégalo-martyrs", -"méga-océan", -"méga-océans", -"méga-ohm", -"méga-ohms", -"mégléno-roumain", "Mehun-sur-Yèvre", "Meigné-le-Vicomte", "Meilhan-sur-Garonne", @@ -15607,109 +8394,33 @@ FR_BASE_EXCEPTIONS = [ "Meilly-sur-Rouvres", "Meix-devant-Virton", "Meix-le-Tige", -"Méjannes-le-Clap", -"Méjannes-lès-Alès", -"mêlé-cass", -"mêlé-casse", -"mêlé-casses", -"mêlé-cassis", -"mele-fila", -"mêle-tout", -"Méligny-le-Grand", -"Méligny-le-Petit", -"méli-mélo", -"mêli-mêlo", -"mélis-mélos", -"mêlis-mêlos", "Mellenbach-Glasbach", "Melleray-la-Vallée", "Melun-Sénart", "Melz-sur-Seine", -"membrano-calcaire", -"Ménestérol-Montignac", -"Ménestreau-en-Villette", "Menetou-Couture", "Menetou-Râtel", "Menetou-Salon", "Menetou-sur-Nahon", -"Ménétréol-sous-Sancerre", -"Ménétréols-sous-Vatan", -"Ménétréol-sur-Sauldre", -"Ménétreux-le-Pitois", -"Menétru-le-Vignoble", -"Menétrux-en-Joux", -"m'enfin", "Mengersgereuth-Hämmern", -"Ménil-Annelles", -"ménil-annellois", -"Ménil-Annellois", -"ménil-annelloise", -"Ménil-Annelloise", -"ménil-annelloises", -"Ménil-Annelloises", -"Ménil-aux-Bois", -"Ménil-de-Senones", -"Ménil-en-Xaintois", -"Ménil-Erreux", -"Ménil-Froger", -"Ménil-Gondouin", -"ménil-gondoyen", -"Ménil-Gondoyen", -"ménil-gondoyenne", -"Ménil-Gondoyenne", -"ménil-gondoyennes", -"Ménil-Gondoyennes", -"ménil-gondoyens", -"Ménil-Gondoyens", -"Ménil-Hermei", -"Ménil-Hubert-en-Exmes", -"Ménil-Hubert-sur-Orne", -"Ménil-Jean", -"Ménil-la-Horgne", -"Ménil-la-Tour", -"Ménil-Lépinois", -"Ménil'muche", -"Ménil-sur-Belvitte", -"Ménil-sur-Saulx", -"Ménil-Vin", -"méningo-encéphalite", -"méningo-gastrique", -"méningo-gastriques", "Mennetou-sur-Cher", -"menthe-coq", +"Menthon-Saint-Bernard", "Menthonnex-en-Bornes", "Menthonnex-sous-Clermont", -"Menthon-Saint-Bernard", "Mentque-Nortbécourt", -"menuisier-moulurier", -"Méolans-Revel", -"Méounes-lès-Montrieux", -"mépiquat-chlorure", -"Merbes-le-Château", +"Menétru-le-Vignoble", +"Menétrux-en-Joux", "Merbes-Sainte-Marie", +"Merbes-le-Château", "Mercey-le-Grand", "Mercey-sur-Saône", "Mercin-et-Vaux", "Merck-Saint-Liévin", "Mercurol-Veaunes", -"mercuroso-mercurique", "Mercury-Gémilly", "Mercus-Garrabet", "Mercy-le-Bas", "Mercy-le-Haut", -"mère-grand", -"Mérens-les-Vals", -"mères-grand", -"Mérey-sous-Montrond", -"Mérey-Vieilley", -"Méricourt-en-Vimeu", -"Méricourt-l'Abbé", -"Méricourt-sur-Somme", -"mérier-blanc", -"mériers-blancs", -"Mérindol-les-Oliviers", -"merisier-pays", -"merisiers-pays", "Merkers-Kieselbach", "Merkwiller-Pechelbronn", "Merle-Leignec", @@ -15717,22 +8428,13 @@ FR_BASE_EXCEPTIONS = [ "Merlieux-et-Fouquerolles", "Meroux-Moval", "Merrey-sur-Arce", -"Merry-la-Vallée", "Merry-Sec", +"Merry-la-Vallée", "Merry-sur-Yonne", "Mers-les-Bains", "Mers-sur-Indre", -"Merville-au-Bois", "Merville-Franceville-Plage", -"Méry-Bissières-en-Auge", -"Méry-Corbon", -"Méry-ès-Bois", -"Méry-la-Bataille", -"Méry-Prémecy", -"Méry-sur-Cher", -"Méry-sur-Marne", -"Méry-sur-Oise", -"Méry-sur-Seine", +"Merville-au-Bois", "Merzig-Wadern", "Mesbrecourt-Richecourt", "Meschers-sur-Gironde", @@ -15741,212 +8443,114 @@ FR_BASE_EXCEPTIONS = [ "Meslay-le-Vidame", "Meslin-l'Évêque", "Mesnard-la-Barotière", -"Mesnières-en-Bray", +"Mesnil-Bruntel", +"Mesnil-Clinchamps", +"Mesnil-Domqueur", +"Mesnil-Follemprise", +"Mesnil-Lettre", +"Mesnil-Martinsart", +"Mesnil-Mauger", +"Mesnil-Panneville", +"Mesnil-Raoul", +"Mesnil-Rousset", +"Mesnil-Saint-Georges", +"Mesnil-Saint-Laurent", +"Mesnil-Saint-Loup", +"Mesnil-Saint-Nicaise", +"Mesnil-Saint-Père", +"Mesnil-Sellières", +"Mesnil-Verclives", +"Mesnil-en-Arrouaise", +"Mesnil-en-Ouche", +"Mesnil-la-Comtesse", +"Mesnil-sous-Vienne", +"Mesnil-sur-l'Estrée", "Mesnils-sur-Iton", -"méso-américain", -"méso-américaine", -"méso-américaines", -"méso-américains", -"Méso-Amérique", -"méso-diastolique", -"méso-diastoliques", -"méso-hygrophile", -"méso-hygrophiles", -"mésosulfuron-méthyl-sodium", -"méso-systolique", -"méso-systoliques", +"Mesnières-en-Bray", "Messey-sur-Grosne", "Messia-sur-Sorne", "Messigny-et-Vantoux", "Messimy-sur-Saône", "Mesves-sur-Loire", -"métacarpo-phalangien", -"Métairies-Saint-Quirin", -"métalaxyl-M", -"métam-sodium", -"métaphysico-théologo-cosmolo-nigologie", -"métaphysico-théologo-cosmolo-nigologies", -"métatarso-phalangien", -"météo-dépendant", -"météo-dépendante", -"météo-dépendantes", -"météo-dépendants", -"méthyl-buténol", -"métirame-zinc", -"mètre-ruban", -"mètres-ruban", -"métro-boulot-dodo", -"mets-en", "Metz-Campagne", -"Metz-en-Couture", -"Metzerlen-Mariastein", -"Metz-le-Comte", "Metz-Robert", -"metz-tesseran", "Metz-Tesseran", -"metz-tesseranne", "Metz-Tesseranne", -"metz-tesserannes", "Metz-Tesserannes", -"metz-tesserans", "Metz-Tesserans", "Metz-Tessy", "Metz-Ville", +"Metz-en-Couture", +"Metz-le-Comte", +"Metzerlen-Mariastein", "Meulan-en-Yvelines", "Meunet-Planches", "Meunet-sur-Vatan", "Meung-sur-Loire", -"meurt-de-faim", -"meurt-de-soif", "Meurthe-et-Moselle", -"meurt-la-faim", "Meuselbach-Schwarzmühle", -"meuse-rhin-yssel", -"Mévergnies-lez-Lens", "Meyrieu-les-Etangs", "Meyrieu-les-Étangs", "Meyrieux-Trouet", "Meyrignac-l'Eglise", "Meyrignac-l'Église", -"Mézidon-Canon", -"Mézières-au-Perche", -"Mézières-en-Brenne", -"Mézières-en-Drouais", -"Mézières-en-Gâtinais", -"Mézières-en-Santerre", -"Mézières-en-Vexin", -"Mézières-lez-Cléry", -"Mézières-sous-Lavardin", -"Mézières-sur-Couesnon", -"Mézières-sur-Issoire", -"Mézières-sur-Oise", -"Mézières-sur-Ponthouin", -"Mézières-sur-Seine", -"Mézy-Moulins", -"Mézy-sur-Seine", -"mezzo-soprano", -"mezzo-sopranos", -"mezzo-termine", -"mezzo-tinto", "Mezzovico-Vira", -"m'halla", -"m'hallas", -"miam-miam", -"miaou-miaou", "Michel-Ange", -"michel-angélesque", -"michel-angélesques", "Michelbach-le-Bas", "Michelbach-le-Haut", -"microélectron-volt", -"microélectron-volts", "Midden-Delfland", "Midden-Drenthe", "Midden-Eierland", -"midi-chlorien", -"midi-chloriens", -"midi-pelle", -"midi-pelles", -"midi-pyrénéen", "Midi-Pyrénéen", "Midi-Pyrénéens", "Midi-Pyrénées", "Midsland-Noord", "Mielen-boven-Aalst", "Mierlo-Hout", -"mieux-disant", -"mieux-disante", -"mieux-disantes", -"mieux-disants", -"mieux-être", "Mignaloux-Beauvoir", "Migné-Auxances", "Milhac-d'Auberoche", "Milhac-de-Nontron", -"militaro-bureaucratique", -"militaro-bureaucratiques", -"militaro-industriel", -"militaro-industrielle", -"militaro-industrielles", -"militaro-industriels", "Milizac-Guipronvel", -"milk-bar", -"milk-bars", -"milk-shake", -"milk-shakes", -"mille-au-godet", -"mille-canton", -"mille-feuille", -"mille-feuilles", -"mille-fleurs", "Mille-Islois", "Millencourt-en-Ponthieu", -"mille-pattes", -"mille-pertuis", -"mille-pieds", -"mille-points", -"milliampère-heure", -"milliampères-heures", -"milli-électron-volt", -"milliélectron-volt", -"milli-électron-volts", -"milliélectron-volts", "Millienhagen-Oebelitz", "Millingen-sur-Rhin", -"milli-ohm", -"milli-ohms", -"Milly-la-Forêt", "Milly-Lamartine", +"Milly-la-Forêt", "Milly-sur-Bradon", "Milly-sur-Thérain", "Milon-la-Chapelle", -"mime-acrobate", +"Min-jun", +"Min-seo", "Minaucourt-le-Mesnil-lès-Hurlus", "Minden-Lübbecke", "Minho-Lima", "Miniac-Morvan", "Miniac-sous-Bécherel", "Minihy-Tréguier", -"ministre-présidence", -"ministre-présidences", -"ministre-président", -"ministres-présidents", -"Min-jun", -"minn'gotain", "Minn'Gotain", -"minn'gotaine", "Minn'Gotaine", -"minn'gotaines", "Minn'Gotaines", -"minn'gotains", "Minn'Gotains", -"Min-seo", -"minus-habens", -"minute-lumière", -"minutes-lumière", "Miossens-Lanusse", "Miquelon-Langlade", "Mirabel-aux-Baronnies", "Mirabel-et-Blacons", +"Miramont-Latour", +"Miramont-Sensacq", "Miramont-d'Astarac", "Miramont-de-Comminges", "Miramont-de-Guyenne", "Miramont-de-Quercy", -"Miramont-Latour", -"Miramont-Sensacq", "Mirandol-Bourgnounac", "Miraval-Cabardes", "Mirebeau-sur-Bèze", -"mire-oeuf", -"mire-œuf", -"mire-oeufs", -"mire-œufs", "Mirepoix-sur-Tarn", "Mireval-Lauragais", "Miribel-Lanchâtre", "Miribel-les-Echelles", "Miribel-les-Échelles", -"miro-miro", "Miserey-Salines", "Misery-Courtion", "Missen-Wilhams", @@ -15956,79 +8560,44 @@ FR_BASE_EXCEPTIONS = [ "Misy-sur-Yonne", "Mitry-Mory", "Mittainvilliers-Vérigny", -"mixed-border", -"mixti-unibinaire", -"m'kahla", -"m'kahlas", -"mobil-home", -"mobil-homes", "Moca-Croce", -"modèle-vue-contrôleur", -"modern-style", -"Moëlan-sur-Mer", -"Mœurs-Verdey", "Moffans-et-Vacheresse", -"mofu-gudur", "Moidieu-Détourbe", "Moigny-sur-Ecole", "Moigny-sur-École", -"moi-même", -"moins-disant", -"moins-disants", -"moins-que-rien", -"moins-value", -"moins-values", "Moinville-la-Jeulin", "Moirans-en-Montagne", "Moirey-Flabas-Crépion", "Moisdon-la-Rivière", -"mois-homme", -"mois-hommes", -"mois-lumière", "Moissac-Bellevue", "Moissac-Vallée-Française", "Moissieu-sur-Dolon", -"moissonner-battre", -"moissonneuse-batteuse", -"moissonneuse-lieuse", -"moissonneuses-batteuses", -"moissonneuses-lieuses", "Moissy-Cramayel", "Moissy-Moulinot", -"moite-moite", -"moitié-moitié", "Moitron-sur-Sarthe", -"mojeño-ignaciano", -"mojeño-javierano", -"mojeño-loretano", -"mojeño-trinitario", "Molenbeek-Saint-Jean", "Molenbeek-Wersbeek", -"Molières-Cavaillac", -"Molières-Glandaz", -"Molières-sur-Cèze", -"Molières-sur-l'Alberte", "Moliets-et-Maa", "Molines-en-Queyras", "Molins-sur-Aube", "Molitg-les-Bains", +"Molières-Cavaillac", +"Molières-Glandaz", +"Molières-sur-Cèze", +"Molières-sur-l'Alberte", "Mollans-sur-Ouvèze", -"Molliens-au-Bois", "Molliens-Dreuil", -"mollo-mollo", -"moment-clé", -"moment-clés", -"moments-clés", +"Molliens-au-Bois", "Monacia-d'Aullène", "Monacia-d'Orezza", "Monassut-Audiracq", "Moncayolle-Larrory-Mendibieu", -"Monceau-en-Ardenne", "Monceau-Imbrechies", -"Monceau-le-Neuf-et-Faucouzy", -"Monceau-lès-Leups", -"Monceau-le-Waast", "Monceau-Saint-Waast", +"Monceau-en-Ardenne", +"Monceau-le-Neuf-et-Faucouzy", +"Monceau-le-Waast", +"Monceau-lès-Leups", "Monceau-sur-Oise", "Monceau-sur-Sambre", "Monceaux-au-Perche", @@ -16036,42 +8605,38 @@ FR_BASE_EXCEPTIONS = [ "Monceaux-l'Abbaye", "Monceaux-le-Comte", "Monceaux-sur-Dordogne", -"Moncé-en-Belin", -"Moncé-en-Saosnois", "Moncel-lès-Lunéville", "Moncel-sur-Seille", "Moncel-sur-Vair", -"Moncetz-l'Abbaye", "Moncetz-Longevas", +"Moncetz-l'Abbaye", "Monchaux-Soreng", "Monchaux-sur-Ecaillon", "Monchaux-sur-Écaillon", "Moncheaux-lès-Frévent", "Monchel-sur-Canche", -"Mönchpfiffel-Nikolausrieth", -"Monchy-au-Bois", "Monchy-Breton", "Monchy-Cayeux", "Monchy-Humières", "Monchy-Lagache", -"Monchy-le-Preux", "Monchy-Saint-Eloi", "Monchy-Saint-Éloi", +"Monchy-au-Bois", +"Monchy-le-Preux", "Monchy-sur-Eu", "Monclar-de-Quercy", "Monclar-sur-Losse", "Moncorneil-Grazan", +"Moncé-en-Belin", +"Moncé-en-Saosnois", "Mondariz-Balneario", "Mondement-Montgivroux", "Mondonville-Saint-Jean", "Mondorf-les-Bains", -"Monestier-d'Ambel", -"Monestier-de-Clermont", "Monestier-Merlines", "Monestier-Port-Dieu", -"Monétay-sur-Allier", -"Monétay-sur-Loire", -"Monêtier-Allemont", +"Monestier-d'Ambel", +"Monestier-de-Clermont", "Monferran-Plavès", "Monferran-Savès", "Monflorite-Lascasas", @@ -16080,42 +8645,89 @@ FR_BASE_EXCEPTIONS = [ "Monistrol-d'Allier", "Monistrol-sur-Loire", "Monlaur-Bernet", -"Monléon-Magnoac", "Monlezun-d'Armagnac", -"monnaie-du-pape", -"Monnetier-Mornex", +"Monléon-Magnoac", "Monnet-la-Ville", +"Monnetier-Mornex", +"Mons-Boubert", +"Mons-en-Barœul", +"Mons-en-Laonnois", +"Mons-en-Montois", +"Mons-en-Pévèle", "Monsempron-Libos", -"monsieur-dame", "Monsteroux-Milieu", +"Mont-Bernanchon", +"Mont-Bonvillers", +"Mont-Cauvaire", +"Mont-Dauphin", +"Mont-Disse", +"Mont-Dol", +"Mont-Dore", +"Mont-Laurent", +"Mont-Louis", +"Mont-Notre-Dame", +"Mont-Ormel", +"Mont-Roc", +"Mont-Saint-Aignan", +"Mont-Saint-Jean", +"Mont-Saint-Léger", +"Mont-Saint-Martin", +"Mont-Saint-Père", +"Mont-Saint-Remy", +"Mont-Saint-Sulpice", +"Mont-Saint-Vincent", +"Mont-Saint-Éloi", +"Mont-Saxonnex", +"Mont-d'Astarac", +"Mont-d'Origny", +"Mont-de-Galié", +"Mont-de-Lans", +"Mont-de-Laval", +"Mont-de-Marrast", +"Mont-de-Marsan", +"Mont-de-Vougney", +"Mont-devant-Sassey", +"Mont-et-Marré", +"Mont-l'Étroit", +"Mont-l'Évêque", +"Mont-le-Vernois", +"Mont-le-Vignoble", +"Mont-lès-Lamarche", +"Mont-lès-Neufchâteau", +"Mont-lès-Seurre", +"Mont-près-Chambord", +"Mont-sous-Vaudrey", +"Mont-sur-Courville", +"Mont-sur-Meurthe", +"Mont-sur-Monnet", "Montacher-Villegardin", -"Montagnac-d'Auberoche", -"Montagnac-la-Crempse", -"Montagnac-Montpezat", -"Montagnac-sur-Auvignon", -"Montagnac-sur-Lède", "Montagna-le-Reconduit", "Montagna-le-Templier", +"Montagnac-Montpezat", +"Montagnac-d'Auberoche", +"Montagnac-la-Crempse", +"Montagnac-sur-Auvignon", +"Montagnac-sur-Lède", "Montagne-Fayel", "Montagney-Servigney", +"Montagny-Sainte-Félicité", "Montagny-en-Vexin", +"Montagny-les-Lanches", "Montagny-lès-Beaune", "Montagny-lès-Buxy", -"Montagny-les-Lanches", "Montagny-lès-Seurre", "Montagny-près-Louhans", "Montagny-près-Yverdon", -"Montagny-Sainte-Félicité", "Montagny-sur-Grosne", "Montaignac-Saint-Hippolyte", +"Montaigu-Zichem", "Montaigu-de-Quercy", -"Montaiguët-en-Forez", "Montaigu-la-Brisette", "Montaigu-le-Blin", "Montaigu-les-Bois", "Montaigut-le-Blanc", "Montaigut-sur-Save", -"Montaigu-Zichem", +"Montaiguët-en-Forez", "Montalba-d'Amélie", "Montalba-le-Château", "Montalet-le-Bois", @@ -16124,9 +8736,9 @@ FR_BASE_EXCEPTIONS = [ "Montaren-et-Saint-Médiers", "Montarlot-lès-Champlitte", "Montarlot-lès-Rioz", +"Montastruc-Savès", "Montastruc-de-Salies", "Montastruc-la-Conseillère", -"Montastruc-Savès", "Montauban-de-Bretagne", "Montauban-de-Luchon", "Montauban-de-Picardie", @@ -16136,21 +8748,21 @@ FR_BASE_EXCEPTIONS = [ "Montboucher-sur-Jabron", "Montbrison-sur-Lez", "Montbrun-Bocage", -"Montbrun-des-Corbières", "Montbrun-Lauragais", +"Montbrun-des-Corbières", "Montbrun-les-Bains", "Montceau-et-Echarnant", "Montceau-et-Écharnant", "Montceau-les-Mines", +"Montceaux-Ragny", +"Montceaux-l'Etoile", +"Montceaux-l'Étoile", "Montceaux-lès-Meaux", "Montceaux-lès-Provins", "Montceaux-lès-Vaudes", -"Montceaux-l'Etoile", -"Montceaux-l'Étoile", -"Montceaux-Ragny", "Montchanin-les-Mines", -"Montclar-de-Comminges", "Montclar-Lauragais", +"Montclar-de-Comminges", "Montclar-sur-Gervanne", "Montcombroux-les-Mines", "Montcornet-en-Ardenne", @@ -16158,47 +8770,25 @@ FR_BASE_EXCEPTIONS = [ "Montcuq-en-Quercy-Blanc", "Montcy-Notre-Dame", "Montcy-Saint-Pierre", -"monte-au-ciel", "Monte-Carlo", -"monte-charge", -"monte-charges", -"monte-courroie", -"monte-courroies", -"monte-en-l'air", -"monte-escalier", -"monte-escaliers", -"Montégut-Arros", -"Montégut-Bourjac", -"Montégut-en-Couserans", -"Montégut-Lauragais", -"Montégut-Plantaurel", -"Montégut-Savès", "Monteignet-sur-l'Andelot", -"monte-jus", -"monte-lait", "Montel-de-Gelat", -"monte-meuble", -"monte-meubles", "Montemor-o-Novo", "Montemor-o-Velho", -"monte-pente", -"monte-pentes", -"monte-plat", -"monte-plats", "Montereau-Fault-Yonne", "Montereau-faut-Yonne", "Montereau-sur-le-Jard", "Montescourt-Lizerolles", "Montesquieu-Avantès", -"Montesquieu-des-Albères", "Montesquieu-Guittaut", "Montesquieu-Lauragais", "Montesquieu-Volvestre", +"Montesquieu-des-Albères", "Montestruc-sur-Gers", "Montet-et-Bouxal", +"Montfaucon-Montigné", "Montfaucon-d'Argonne", "Montfaucon-en-Velay", -"Montfaucon-Montigné", "Montferrand-du-Périgord", "Montferrand-la-Fare", "Montferrand-le-Château", @@ -16211,36 +8801,35 @@ FR_BASE_EXCEPTIONS = [ "Montfort-sur-Boulzane", "Montfort-sur-Meu", "Montfort-sur-Risle", -"Montgaillard-de-Salies", "Montgaillard-Lauragais", +"Montgaillard-de-Salies", "Montgaillard-sur-Save", -"Montgé-en-Goële", "Montgru-Saint-Hilaire", +"Montgé-en-Goële", "Monthou-sur-Bièvre", "Monthou-sur-Cher", "Monthureux-le-Sec", "Monthureux-sur-Saône", -"monti-corcellois", "Monti-Corcellois", -"monti-corcelloise", "Monti-Corcelloise", -"monti-corcelloises", "Monti-Corcelloises", "Montier-en-Der", "Montier-en-l'Isle", "Montiers-sur-Saulx", "Monties-Aussos", "Montignac-Charente", +"Montignac-Toupinerie", "Montignac-de-Lauzun", "Montignac-le-Coq", -"Montignac-Toupinerie", -"Montigné-le-Brillant", -"Montigné-lès-Rairies", -"Montigné-sur-Moine", -"Montignies-lez-Lens", "Montignies-Saint-Christophe", +"Montignies-lez-Lens", "Montignies-sur-Roc", "Montignies-sur-Sambre", +"Montigny-Lencoup", +"Montigny-Lengrain", +"Montigny-Montfort", +"Montigny-Mornay-Villeneuve-sur-Vingeanne", +"Montigny-Saint-Barthélemy", "Montigny-aux-Amognes", "Montigny-devant-Sassey", "Montigny-en-Arrouaise", @@ -16255,22 +8844,17 @@ FR_BASE_EXCEPTIONS = [ "Montigny-le-Franc", "Montigny-le-Gannelon", "Montigny-le-Guesdier", -"Montigny-Lencoup", -"Montigny-Lengrain", +"Montigny-le-Teigneux", +"Montigny-le-Tilleul", +"Montigny-les-Jongleurs", +"Montigny-les-Monts", "Montigny-lès-Arsures", "Montigny-lès-Cherlieu", "Montigny-lès-Condé", "Montigny-lès-Cormeilles", -"Montigny-les-Jongleurs", "Montigny-lès-Metz", -"Montigny-les-Monts", "Montigny-lès-Vaucouleurs", "Montigny-lès-Vesoul", -"Montigny-le-Teigneux", -"Montigny-le-Tilleul", -"Montigny-Montfort", -"Montigny-Mornay-Villeneuve-sur-Vingeanne", -"Montigny-Saint-Barthélemy", "Montigny-sous-Marle", "Montigny-sur-Armançon", "Montigny-sur-Aube", @@ -16278,25 +8862,24 @@ FR_BASE_EXCEPTIONS = [ "Montigny-sur-Canne", "Montigny-sur-Chiers", "Montigny-sur-Crécy", -"Montigny-sur-l'Ain", -"Montigny-sur-l'Hallue", "Montigny-sur-Loing", "Montigny-sur-Meuse", "Montigny-sur-Vence", "Montigny-sur-Vesle", +"Montigny-sur-l'Ain", +"Montigny-sur-l'Hallue", +"Montigné-le-Brillant", +"Montigné-lès-Rairies", +"Montigné-sur-Moine", "Montilly-sur-Noireau", -"montis-fagussin", "Montis-Fagussin", -"montis-fagussine", "Montis-Fagussine", -"montis-fagussines", "Montis-Fagussines", -"montis-fagussins", "Montis-Fagussins", "Montjean-sur-Loire", +"Montjoie-Saint-Martin", "Montjoie-en-Couserans", "Montjoie-le-Château", -"Montjoie-Saint-Martin", "Montjustin-et-Velotte", "Montlaur-en-Diois", "Montlay-en-Auxois", @@ -16325,30 +8908,23 @@ FR_BASE_EXCEPTIONS = [ "Montpezat-sous-Bauzon", "Montpon-Ménestérol", "Montpont-en-Bresse", -"Montréal-la-Cluse", -"Montréal-les-Sources", -"montréalo-centrisme", -"montre-bracelet", -"montre-chronomètre", -"Montredon-des-Corbières", "Montredon-Labessonnié", -"montres-bracelets", -"montres-chronomètres", -"Montreuil-au-Houlme", -"Montreuil-aux-Lions", +"Montredon-des-Corbières", "Montreuil-Bellay", "Montreuil-Bonnin", +"Montreuil-Juigné", +"Montreuil-Poulay", +"Montreuil-au-Houlme", +"Montreuil-aux-Lions", "Montreuil-des-Landes", "Montreuil-en-Auge", "Montreuil-en-Caux", "Montreuil-en-Touraine", -"Montreuil-Juigné", -"Montreuil-la-Cambe", "Montreuil-l'Argillé", +"Montreuil-la-Cambe", "Montreuil-le-Chétif", "Montreuil-le-Gast", "Montreuil-le-Henri", -"Montreuil-Poulay", "Montreuil-sous-Bois", "Montreuil-sous-Pérouse", "Montreuil-sur-Barse", @@ -16359,8 +8935,8 @@ FR_BASE_EXCEPTIONS = [ "Montreuil-sur-Loir", "Montreuil-sur-Lozon", "Montreuil-sur-Maine", -"Montreuil-sur-Thérain", "Montreuil-sur-Thonnance", +"Montreuil-sur-Thérain", "Montreux-Château", "Montreux-Jeune", "Montreux-Vieux", @@ -16369,38 +8945,46 @@ FR_BASE_EXCEPTIONS = [ "Montrichard-Val-de-Cher", "Montricher-Albanne", "Montrieux-en-Sologne", -"Montrœul-au-Bois", -"Montrœul-sur-Haine", "Montrol-Sénard", "Montrond-le-Château", "Montrond-les-Bains", +"Montréal-la-Cluse", +"Montréal-les-Sources", +"Montrœul-au-Bois", +"Montrœul-sur-Haine", +"Monts-en-Bessin", +"Monts-en-Ternois", +"Monts-sur-Guesnes", "Montsauche-les-Settons", "Montsecret-Clairefougère", -"Montségur-sur-Lauzon", "Montsinéry-Tonnegrande", +"Montségur-sur-Lauzon", "Montureux-et-Prantigny", "Montureux-lès-Baulay", "Montval-sur-Loir", +"Montégut-Arros", +"Montégut-Bourjac", +"Montégut-Lauragais", +"Montégut-Plantaurel", +"Montégut-Savès", +"Montégut-en-Couserans", +"Monétay-sur-Allier", +"Monétay-sur-Loire", +"Monêtier-Allemont", "Moon-sur-Elle", -"Moorea-Maiao", "Moor-Rolofshagen", -"moque-dieu", +"Moorea-Maiao", "Morainville-Jouveaux", "Morainville-près-Lieurey", "Morannes-sur-Sarthe", "Moras-en-Valloire", -"mords-cheval", -"Mörel-Filet", -"Morêtel-de-Mailles", "Moret-sur-Loing", "Morey-Saint-Denis", -"Mörfelden-Walldorf", "Morgenröthe-Rautenkranz", "Morgny-en-Thiérache", "Morgny-la-Pommeraye", -"Morières-lès-Avignon", "Morigny-Champigny", -"Möriken-Wildegg", +"Morières-lès-Avignon", "Morlanwelz-Mariemont", "Morlhon-le-Haut", "Mormant-sur-Vernisson", @@ -16409,7 +8993,6 @@ FR_BASE_EXCEPTIONS = [ "Mornay-Berry", "Mornay-sur-Allier", "Morne-à-l'Eau", -"morphine-base", "Morsang-sur-Orge", "Morsang-sur-Seine", "Morsbronn-les-Bains", @@ -16419,93 +9002,42 @@ FR_BASE_EXCEPTIONS = [ "Mortagne-sur-Gironde", "Mortagne-sur-Sèvre", "Mortain-Bocage", -"mort-aux-rats", -"mort-bois", -"mort-chien", -"mort-de-chien", -"mort-dieu", "Morteaux-Couliboeuf", +"Morteaux-Coulibœuf", "Morteaux-Coulibœuf", -"morte-eau", "Mortefontaine-en-Thelle", -"morte-paye", -"morte-payes", "Morterolles-sur-Semme", -"morte-saison", -"mortes-eaux", "Mortes-Frontières", -"mortes-payes", -"mortes-saisons", -"mortes-vivantes", -"morte-vivante", -"mort-né", -"mort-née", -"mort-nées", -"mort-nés", -"mort-plain", -"mort-plains", -"morts-bois", -"morts-chiens", -"morts-flats", -"morts-terrains", -"morts-vivants", -"mort-terrain", -"mort-vivant", "Morville-en-Beauce", "Morville-lès-Vic", -"Morvillers-Saint-Saturnin", "Morville-sur-Andelle", "Morville-sur-Nied", "Morville-sur-Seille", +"Morvillers-Saint-Saturnin", "Mory-Montcrux", -"moteur-fusée", -"moteurs-fusées", +"Morêtel-de-Mailles", "Motey-Besuche", "Motey-sur-Saône", -"moto-cross", -"moto-crotte", -"moto-crottes", -"moto-école", -"moto-écoles", -"moto-réducteur", -"moto-réducteurs", "Mouans-Sartoux", -"mouche-araignée", -"mouche-sans-raison", -"mouche-scorpion", -"mouches-sans-raison", -"mouches-scorpions", "Mouchy-le-Châtel", "Mougon-Thorigné", -"mouille-bouche", +"Mouilleron-Saint-Germain", "Mouilleron-en-Pareds", "Mouilleron-le-Captif", -"Mouilleron-Saint-Germain", -"moule-bite", -"moule-burnes", -"moule-fesses", -"moules-burnes", -"Moulès-et-Baucels", -"Moulézan-et-Montagnac", "Mouliets-et-Villemartin", -"moulin-à-vent", -"Moulin-l'Évêque", "Moulin-Mage", -"moulin-mageois", "Moulin-Mageois", -"moulin-mageoise", "Moulin-Mageoise", -"moulin-mageoises", "Moulin-Mageoises", "Moulin-Neuf", -"moulins-à-vent", +"Moulin-l'Évêque", +"Moulin-sous-Touvent", "Moulins-Engilbert", +"Moulins-Saint-Hubert", "Moulins-en-Tonnerrois", "Moulins-la-Marche", "Moulins-le-Carbonnel", "Moulins-lès-Metz", -"Moulin-sous-Touvent", -"Moulins-Saint-Hubert", "Moulins-sous-Fléron", "Moulins-sur-Céphons", "Moulins-sur-Orne", @@ -16513,6 +9045,8 @@ FR_BASE_EXCEPTIONS = [ "Moulins-sur-Yèvre", "Moulis-en-Médoc", "Moult-Chicheboville", +"Moulès-et-Baucels", +"Moulézan-et-Montagnac", "Mounes-Prohencoux", "Mourioux-Vieilleville", "Mourmelon-le-Grand", @@ -16522,262 +9056,170 @@ FR_BASE_EXCEPTIONS = [ "Mours-Saint-Eusèbe", "Mourvilles-Basses", "Mourvilles-Hautes", -"Mousseaux-lès-Bray", "Mousseaux-Neuville", +"Mousseaux-lès-Bray", "Mousseaux-sur-Seine", +"Moussy-Verneuil", "Moussy-le-Neuf", "Moussy-le-Vieux", -"Moussy-Verneuil", +"Moustier-Ventadour", "Moustier-en-Fagne", "Moustiers-Sainte-Marie", -"Moustier-Ventadour", -"moustiques-tigres", -"moustique-tigre", "Moustoir-Ac", "Moustoir-Remungol", "Moutaine-Aresches", "Mouterre-Silly", "Mouterre-sur-Blourde", -"Mouthier-en-Bresse", "Mouthier-Haute-Pierre", +"Mouthier-en-Bresse", "Mouthiers-sur-Boëme", -"Moutier-d'Ahun", "Moutier-Malcard", "Moutier-Rozeille", +"Moutier-d'Ahun", +"Moutiers-Saint-Jean", "Moutiers-au-Perche", "Moutiers-en-Puisaye", "Moutiers-les-Mauxfaits", -"Moutiers-Saint-Jean", "Moutiers-sous-Argenton", "Moutiers-sous-Chantemerle", "Moutiers-sur-le-Lay", -"mouton-noirisa", -"mouton-noirisai", -"mouton-noirisaient", -"mouton-noirisais", -"mouton-noirisait", -"mouton-noirisâmes", -"mouton-noirisant", -"mouton-noirisas", -"mouton-noirisasse", -"mouton-noirisassent", -"mouton-noirisasses", -"mouton-noirisassiez", -"mouton-noirisassions", -"mouton-noirisât", -"mouton-noirisâtes", -"mouton-noirise", -"mouton-noirisé", -"mouton-noirisée", -"mouton-noirisées", -"mouton-noirisent", -"mouton-noiriser", -"mouton-noirisera", -"mouton-noiriserai", -"mouton-noiriseraient", -"mouton-noiriserais", -"mouton-noiriserait", -"mouton-noiriseras", -"mouton-noirisèrent", -"mouton-noiriserez", -"mouton-noiriseriez", -"mouton-noiriserions", -"mouton-noiriserons", -"mouton-noiriseront", -"mouton-noirises", -"mouton-noirisés", -"mouton-noirisez", -"mouton-noirisiez", -"mouton-noirisions", -"mouton-noirisons", -"mouve-chaux", "Moux-en-Morvan", "Mouy-sur-Seine", "Mouzeuil-Saint-Martin", "Mouzieys-Panens", "Mouzieys-Teulet", -"Moÿ-de-l'Aisne", "Moyencourt-lès-Poix", "Moyenne-Franconie", -"moyens-ducs", "Moyeuvre-Grande", "Moyeuvre-Petite", "Mozé-sur-Louet", -"m-paiement", -"m-paiements", -"m'sieur", -"M'Tsangamouji", +"Moëlan-sur-Mer", +"Moÿ-de-l'Aisne", "Muad-Dib", -"muco-pus", -"mud-minnow", "Muespach-le-Haut", "Muhlbach-sur-Bruche", "Muhlbach-sur-Munster", -"Mühlhausen-Ehingen", "Muides-sur-Loire", "Muille-Villette", -"mule-jenny", -"Mülheim-Kärlich", -"mull-jenny", -"multiplate-forme", -"multiplates-formes", -"mu-métal", -"Mümliswil-Ramiswil", "Muncq-Nieurlet", "Muneville-le-Bingard", "Muneville-sur-Mer", -"Münster-Geschinen", -"Münster-Sarmsheim", +"Mur-de-Barrez", +"Mur-de-Sologne", "Murat-le-Quaire", "Murat-sur-Vèbre", -"Mur-de-Barrez", -"Mûr-de-Bretagne", -"Mur-de-Sologne", "Muret-et-Crouttes", "Muret-le-Château", -"murnau-werdenfels", -"mur-rideau", -"Mûrs-Erigné", -"Mûrs-Érigné", "Murs-et-Gélignieux", -"murs-rideaux", "Murtin-Bogny", "Murtin-et-Bogny", "Murtin-et-le-Châtelet", "Murviel-lès-Béziers", "Murviel-lès-Montpellier", -"musculo-cutané", -"musettes-repas", -"music-hall", -"music-hallesque", -"music-hallesques", -"music-halls", "Mussey-sur-Marne", "Mussy-la-Fosse", "Mussy-la-Ville", "Mussy-sous-Dun", "Mussy-sur-Seine", -"mu'ugalavyáni", -"n-3", +"Mœurs-Verdey", +"Mâcot-la-Plagne", +"Méjannes-le-Clap", +"Méjannes-lès-Alès", +"Méligny-le-Grand", +"Méligny-le-Petit", +"Ménestreau-en-Villette", +"Ménestérol-Montignac", +"Ménil'muche", +"Ménil-Annelles", +"Ménil-Annellois", +"Ménil-Annelloise", +"Ménil-Annelloises", +"Ménil-Erreux", +"Ménil-Froger", +"Ménil-Gondouin", +"Ménil-Gondoyen", +"Ménil-Gondoyenne", +"Ménil-Gondoyennes", +"Ménil-Gondoyens", +"Ménil-Hermei", +"Ménil-Hubert-en-Exmes", +"Ménil-Hubert-sur-Orne", +"Ménil-Jean", +"Ménil-Lépinois", +"Ménil-Vin", +"Ménil-aux-Bois", +"Ménil-de-Senones", +"Ménil-en-Xaintois", +"Ménil-la-Horgne", +"Ménil-la-Tour", +"Ménil-sur-Belvitte", +"Ménil-sur-Saulx", +"Ménétreux-le-Pitois", +"Ménétréol-sous-Sancerre", +"Ménétréol-sur-Sauldre", +"Ménétréols-sous-Vatan", +"Méolans-Revel", +"Méounes-lès-Montrieux", +"Mérens-les-Vals", +"Mérey-Vieilley", +"Mérey-sous-Montrond", +"Méricourt-en-Vimeu", +"Méricourt-l'Abbé", +"Méricourt-sur-Somme", +"Mérindol-les-Oliviers", +"Méry-Bissières-en-Auge", +"Méry-Corbon", +"Méry-Prémecy", +"Méry-la-Bataille", +"Méry-sur-Cher", +"Méry-sur-Marne", +"Méry-sur-Oise", +"Méry-sur-Seine", +"Méry-ès-Bois", +"Méso-Amérique", +"Métairies-Saint-Quirin", +"Mévergnies-lez-Lens", +"Mézidon-Canon", +"Mézières-au-Perche", +"Mézières-en-Brenne", +"Mézières-en-Drouais", +"Mézières-en-Gâtinais", +"Mézières-en-Santerre", +"Mézières-en-Vexin", +"Mézières-lez-Cléry", +"Mézières-sous-Lavardin", +"Mézières-sur-Couesnon", +"Mézières-sur-Issoire", +"Mézières-sur-Oise", +"Mézières-sur-Ponthouin", +"Mézières-sur-Seine", +"Mézy-Moulins", +"Mézy-sur-Seine", +"Mönchpfiffel-Nikolausrieth", +"Mörel-Filet", +"Mörfelden-Walldorf", +"Möriken-Wildegg", +"Mûr-de-Bretagne", +"Mûrs-Erigné", +"Mûrs-Érigné", +"Mühlhausen-Ehingen", +"Mülheim-Kärlich", +"Mümliswil-Ramiswil", +"Münster-Geschinen", +"Münster-Sarmsheim", +"Mœurs-Verdey", +"N'Djamena", +"N'Djaména", +"N'Tcham", +"N'dorola", +"N,N-dinitronitramide", "N-(4-hydroxyphényl)éthanamide", -"n-6", -"n-9", "N-acétylcystéine", -"Nachrodt-Wiblingwerde", -"Nadaillac-de-Rouge", -"na-dené", -"na-déné", -"Nagel-Séez-Mesnil", -"Nages-et-Solorgues", -"Nagorno-Karabakh", -"Nagorny-Karabagh", -"Nagorny-Karabakh", -"Nago-Torbole", -"Nahetal-Waldau", -"Nainville-les-Roches", -"n-aire", -"n-aires", -"Naisey-les-Granges", -"Naives-en-Blois", -"Naives-Rosières", -"Naix-aux-Forges", -"name-dropping", -"nam-nam", -"nam-nams", -"Nampcelles-la-Cour", -"Namps-au-Mont", -"Namps-Maisnil", -"Nampteuil-sous-Muret", -"Nanc-lès-Saint-Amour", -"Nançois-le-Grand", -"Nançois-sur-Ornain", -"Nancray-sur-Rimarde", -"Nancy-sur-Cluses", -"Nandin-sur-Aisne", -"nano-ohm", -"nano-ohms", -"Nans-les-Pins", -"Nan-sous-Thil", -"Nans-sous-Sainte-Anne", -"Nanteau-sur-Essonne", -"Nanteau-sur-Lunain", -"Nantes-en-Ratier", -"Nanteuil-Auriac-de-Bourzac", -"Nanteuil-en-Vallée", -"Nanteuil-la-Forêt", -"Nanteuil-la-Fosse", -"Nanteuil-le-Haudouin", -"Nanteuil-lès-Meaux", -"Nanteuil-Notre-Dame", -"Nanteuil-sur-Aisne", -"Nanteuil-sur-Marne", -"Nant-le-Grand", -"Nant-le-Petit", -"naphtoxy-2-acétamide", -"Napoléon-Vendée", -"narco-État", -"narco-États", -"narco-guérilla", -"narco-guérillas", -"narcotico-âcre", -"narco-trafiquant", -"narco-trafiquants", -"naso-génien", -"naso-lobaire", -"naso-lobaires", -"naso-oculaire", -"naso-palatin", -"naso-palpébral", -"naso-sourcilier", -"naso-transversal", -"Nassandres-sur-Risle", -"nat-gadaw", -"nat-gadaws", -"nationale-socialiste", -"nationales-socialistes", -"national-socialisme", -"national-socialiste", -"nationaux-socialistes", -"nat-kadaw", -"nat-kadaws", -"natro-feldspat", -"natro-feldspats", -"natu-majorité", -"Naujac-sur-Mer", -"Naujan-et-Postiac", -"Naussac-Fontanes", -"nautico-estival", -"Navailles-Angos", -"navarro-aragonais", -"navarro-labourdin", -"Nâves-Parmelan", -"navire-citerne", -"navire-école", -"navire-mère", -"navires-citernes", -"navires-écoles", -"navires-mères", -"navire-usine", -"Nay-Bourdettes", -"Nayemont-les-Fosses", -"Nazelles-Négron", -"Naz-Sciaves", -"n-boule", -"n-boules", -"n-butane", -"n-butanes", -"n-butyle", -"n-cube", -"n-cubes", -"N.-D.", -"n'dama", -"n'damas", "N-déméthyla", "N-déméthylai", "N-déméthylaient", "N-déméthylais", "N-déméthylait", -"N-déméthylâmes", "N-déméthylant", "N-déméthylas", "N-déméthylasse", @@ -16785,12 +9227,7 @@ FR_BASE_EXCEPTIONS = [ "N-déméthylasses", "N-déméthylassiez", "N-déméthylassions", -"N-déméthylât", -"N-déméthylâtes", "N-déméthyle", -"N-déméthylé", -"N-déméthylée", -"N-déméthylées", "N-déméthylent", "N-déméthyler", "N-déméthylera", @@ -16799,24 +9236,118 @@ FR_BASE_EXCEPTIONS = [ "N-déméthylerais", "N-déméthylerait", "N-déméthyleras", -"N-déméthylèrent", "N-déméthylerez", "N-déméthyleriez", "N-déméthylerions", "N-déméthylerons", "N-déméthyleront", "N-déméthyles", -"N-déméthylés", "N-déméthylez", "N-déméthyliez", "N-déméthylions", "N-déméthylons", -"n-dimensionnel", -"N'Djamena", -"N'Djaména", +"N-déméthylâmes", +"N-déméthylât", +"N-déméthylâtes", +"N-déméthylèrent", +"N-déméthylé", +"N-déméthylée", +"N-déméthylées", +"N-déméthylés", +"N-méthyla", +"N-méthylai", +"N-méthylaient", +"N-méthylais", +"N-méthylait", +"N-méthylant", +"N-méthylas", +"N-méthylasse", +"N-méthylassent", +"N-méthylasses", +"N-méthylassiez", +"N-méthylassions", +"N-méthyle", +"N-méthylent", +"N-méthyler", +"N-méthylera", +"N-méthylerai", +"N-méthyleraient", +"N-méthylerais", +"N-méthylerait", +"N-méthyleras", +"N-méthylerez", +"N-méthyleriez", +"N-méthylerions", +"N-méthylerons", +"N-méthyleront", +"N-méthyles", +"N-méthylez", +"N-méthyliez", +"N-méthylions", +"N-méthylons", +"N-méthylâmes", +"N-méthylât", +"N-méthylâtes", +"N-méthylèrent", +"N-méthylé", +"N-méthylée", +"N-méthylées", +"N-méthylés", +"N-éthyléthanamine", +"N.-D.", +"N.-W.", "NDM-1", -"N'dorola", -"Néant-sur-Yvel", +"Nachrodt-Wiblingwerde", +"Nadaillac-de-Rouge", +"Nagel-Séez-Mesnil", +"Nages-et-Solorgues", +"Nago-Torbole", +"Nagorno-Karabakh", +"Nagorny-Karabagh", +"Nagorny-Karabakh", +"Nahetal-Waldau", +"Nainville-les-Roches", +"Naisey-les-Granges", +"Naives-Rosières", +"Naives-en-Blois", +"Naix-aux-Forges", +"Nampcelles-la-Cour", +"Namps-Maisnil", +"Namps-au-Mont", +"Nampteuil-sous-Muret", +"Nan-sous-Thil", +"Nanc-lès-Saint-Amour", +"Nancray-sur-Rimarde", +"Nancy-sur-Cluses", +"Nandin-sur-Aisne", +"Nans-les-Pins", +"Nans-sous-Sainte-Anne", +"Nant-le-Grand", +"Nant-le-Petit", +"Nanteau-sur-Essonne", +"Nanteau-sur-Lunain", +"Nantes-en-Ratier", +"Nanteuil-Auriac-de-Bourzac", +"Nanteuil-Notre-Dame", +"Nanteuil-en-Vallée", +"Nanteuil-la-Forêt", +"Nanteuil-la-Fosse", +"Nanteuil-le-Haudouin", +"Nanteuil-lès-Meaux", +"Nanteuil-sur-Aisne", +"Nanteuil-sur-Marne", +"Nançois-le-Grand", +"Nançois-sur-Ornain", +"Napoléon-Vendée", +"Nassandres-sur-Risle", +"Naujac-sur-Mer", +"Naujan-et-Postiac", +"Naussac-Fontanes", +"Navailles-Angos", +"Nay-Bourdettes", +"Nayemont-les-Fosses", +"Naz-Sciaves", +"Nazelles-Négron", "Neaufles-Auvergny", "Neaufles-Saint-Martin", "Neaufles-sur-Risle", @@ -16828,211 +9359,148 @@ FR_BASE_EXCEPTIONS = [ "Neckar-Odenwald", "Neder-Betuwe", "Neder-Hardinxveld", +"Neder-Over-Heembeek", +"Neder-over-Heembeek", "Nederhemert-Noord", "Nederhemert-Zuid", -"Neder-over-Heembeek", -"Neder-Over-Heembeek", "Nederweert-Eind", "Nederzwalm-Hermelgem", "Neewiller-près-Lauterbourg", -"néfaste-food", -"néfaste-foods", -"nègre-soie", -"nègres-soies", -"negro-spiritual", -"negro-spirituals", -"nègue-chien", -"nègue-fol", "Nehwiller-près-Wœrth", "Neige-Côtier", "Neiße-Malxetal", -"ne-m'oubliez-pas", "Nempont-Saint-Firmin", "Nemsdorf-Göhrendorf", -"Néons-sur-Creuse", -"néphro-angiosclérose", -"néphro-angioscléroses", -"néphro-gastrique", -"néphro-urétérectomie", -"néphro-urétérectomies", -"neptuno-plutonien", -"neptuno-plutonienne", -"neptuno-plutoniens", -"nerf-ferrure", -"nerf-férure", -"Néris-les-Bains", -"Néronde-sur-Dore", "Nerville-la-Forêt", -"Nesle-et-Massoult", "Nesle-Hodeng", +"Nesle-Normandeuse", +"Nesle-et-Massoult", +"Nesle-l'Hôpital", "Nesle-la-Reposte", "Nesle-le-Repons", -"Nesle-l'Hôpital", -"Nesle-Normandeuse", "Nesles-la-Gilberde", "Nesles-la-Montagne", "Nesles-la-Vallée", -"net-citoyen", -"net-citoyens", -"N-éthyléthanamine", -"nettoie-pipe", "Neu-Anspach", "Neu-Bamberg", +"Neu-Eichenberg", +"Neu-Isenburg", +"Neu-Moresnet", +"Neu-Seeland", +"Neu-Ulm", "Neublans-Abergement", "Neubourg-sur-le-Danube", "Neuburg-Schrobenhausen", "Neuchâtel-Urtière", "Neudorf-Bornstein", -"Neu-Eichenberg", "Neuendorf-Sachsenbande", "Neuenkirchen-Vörden", "Neuf-Berquin", -"neuf-berquinois", "Neuf-Berquinois", -"neuf-berquinoise", "Neuf-Berquinoise", -"neuf-berquinoises", "Neuf-Berquinoises", "Neuf-Brisach", -"neuf-cents", -"Neufchâtel-en-Bray", -"Neufchâtel-en-Saosnois", -"Neufchâtel-Hardelot", -"Neufchâtel-sur-Aisne", "Neuf-Eglise", -"Neuf-Église", "Neuf-Marché", "Neuf-Mesnil", +"Neuf-Église", +"Neufchâtel-Hardelot", +"Neufchâtel-en-Bray", +"Neufchâtel-en-Saosnois", +"Neufchâtel-sur-Aisne", "Neufmoutiers-en-Brie", "Neufvy-sur-Aronde", "Neugartheim-Ittlenheim", "Neuhaus-Schierschnitz", "Neuillay-les-Bois", -"Neuillé-le-Lierre", -"Neuillé-Pont-Pierre", +"Neuilly-Plaisance", +"Neuilly-Saint-Front", "Neuilly-en-Donjon", "Neuilly-en-Dun", "Neuilly-en-Sancerre", "Neuilly-en-Thelle", "Neuilly-en-Vexin", +"Neuilly-l'Evêque", +"Neuilly-l'Hôpital", +"Neuilly-l'Évêque", "Neuilly-la-Forêt", "Neuilly-le-Bisson", "Neuilly-le-Brignon", "Neuilly-le-Dien", "Neuilly-le-Malherbe", "Neuilly-le-Réal", -"Neuilly-lès-Dijon", "Neuilly-le-Vendin", -"Neuilly-l'Evêque", -"Neuilly-l'Évêque", -"Neuilly-l'Hôpital", -"Neuilly-Plaisance", -"Neuilly-Saint-Front", +"Neuilly-lès-Dijon", "Neuilly-sous-Clermont", "Neuilly-sur-Eure", "Neuilly-sur-Marne", "Neuilly-sur-Seine", "Neuilly-sur-Suize", -"Neu-Isenburg", +"Neuillé-Pont-Pierre", +"Neuillé-le-Lierre", "Neukirchen-Balbini", "Neukirchen-Vluyn", "Neumagen-Dhron", -"Neu-Moresnet", "Neung-sur-Beuvron", -"Neunkirchen-lès-Bouzonville", -"Neunkirchen-Seelscheid", "Neunkirch-lès-Sarreguemines", +"Neunkirchen-Seelscheid", +"Neunkirchen-lès-Bouzonville", "Neurey-en-Vaux", "Neurey-lès-la-Demie", -"neuro-acoustique", -"neuro-acoustiques", -"neuro-anatomie", -"neuro-anatomies", -"neuro-humoral", -"neuro-humorale", -"neuro-humorales", -"neuro-humoraux", -"neuro-imagerie", -"neuro-imageries", -"neuro-linguistique", -"neuro-linguistiques", -"neuro-musculaire", -"neuro-musculaires", -"neuro-stimulation", -"neuro-végétatif", -"neuro-végétatifs", -"neuro-végétative", -"neuro-végétatives", "Neusalza-Spremberg", -"Neu-Seeland", "Neussargues-Moissac", "Neustadt-Glewe", -"neutro-alcalin", -"Neu-Ulm", "Neuve-Chapelle", -"neuve-chapellois", "Neuve-Chapellois", -"neuve-chapelloise", "Neuve-Chapelloise", -"neuve-chapelloises", "Neuve-Chapelloises", "Neuve-Eglise", -"Neuve-Église", -"Neuvéglise-sur-Truyère", -"neuve-grangeais", "Neuve-Grangeais", -"neuve-grangeaise", "Neuve-Grangeaise", -"neuve-grangeaises", "Neuve-Grangeaises", +"Neuve-Maison", +"Neuve-Église", "Neuvelle-lès-Champlitte", "Neuvelle-lès-Cromary", "Neuvelle-lès-Grancey", -"Neuvelle-lès-la-Charité", "Neuvelle-lès-Voisey", -"Neuve-Maison", +"Neuvelle-lès-la-Charité", "Neuves-Maisons", "Neuvic-Entier", -"Neuvicq-le-Château", "Neuvicq-Montguyon", -"Neuville-au-Bois", -"Neuville-au-Cornet", -"Neuville-au-Plain", -"Neuville-aux-Bois", +"Neuvicq-le-Château", "Neuville-Bosc", -"neuville-boscien", "Neuville-Boscien", -"neuville-boscienne", "Neuville-Boscienne", -"neuville-bosciennes", "Neuville-Bosciennes", -"neuville-bosciens", "Neuville-Bosciens", "Neuville-Bourjonval", "Neuville-Coppegueule", "Neuville-Day", +"Neuville-Ferrières", +"Neuville-Saint-Amand", +"Neuville-Saint-Rémy", +"Neuville-Saint-Vaast", +"Neuville-Vitasse", +"Neuville-au-Bois", +"Neuville-au-Cornet", +"Neuville-au-Plain", +"Neuville-aux-Bois", "Neuville-de-Poitou", "Neuville-en-Avesnois", "Neuville-en-Beaumont", "Neuville-en-Condroz", "Neuville-en-Ferrain", "Neuville-en-Verdunois", -"Neuville-Ferrières", "Neuville-les-Dames", +"Neuville-lez-Beaulieu", "Neuville-lès-Decize", "Neuville-lès-Dieppe", +"Neuville-lès-Lœuilly", "Neuville-lès-Lœuilly", "Neuville-lès-This", "Neuville-lès-Vaucouleurs", -"Neuville-lez-Beaulieu", "Neuville-près-Sées", -"Neuviller-la-Roche", -"Neuviller-lès-Badonviller", -"Neuvillers-sur-Fave", -"Neuviller-sur-Moselle", -"Neuville-Saint-Amand", -"Neuville-Saint-Rémy", -"Neuville-Saint-Vaast", "Neuville-sous-Arzillières", "Neuville-sous-Montreuil", "Neuville-sur-Ailette", @@ -17043,138 +9511,71 @@ FR_BASE_EXCEPTIONS = [ "Neuville-sur-Margival", "Neuville-sur-Oise", "Neuville-sur-Ornain", -"Neuville-sur-Saône", "Neuville-sur-Sarthe", +"Neuville-sur-Saône", "Neuville-sur-Seine", "Neuville-sur-Touques", "Neuville-sur-Vanne", "Neuville-sur-Vannes", +"Neuviller-la-Roche", +"Neuviller-lès-Badonviller", +"Neuviller-sur-Moselle", +"Neuvillers-sur-Fave", "Neuvillette-en-Charnie", -"Neuville-Vitasse", "Neuvilly-en-Argonne", -"Neuvy-au-Houlme", "Neuvy-Bouin", "Neuvy-Deux-Clochers", +"Neuvy-Grandchamp", +"Neuvy-Pailloux", +"Neuvy-Saint-Sépulchre", +"Neuvy-Sautour", +"Neuvy-Sautourien", +"Neuvy-Sautourienne", +"Neuvy-Sautouriennes", +"Neuvy-Sautouriens", +"Neuvy-au-Houlme", "Neuvy-en-Beauce", "Neuvy-en-Champagne", "Neuvy-en-Dunois", "Neuvy-en-Mauges", "Neuvy-en-Sullias", -"Neuvy-Grandchamp", "Neuvy-le-Barrois", "Neuvy-le-Roi", -"Neuvy-Pailloux", -"Neuvy-Saint-Sépulchre", -"Neuvy-Sautour", -"neuvy-sautourien", -"Neuvy-Sautourien", -"neuvy-sautourienne", -"Neuvy-Sautourienne", -"neuvy-sautouriennes", -"Neuvy-Sautouriennes", -"neuvy-sautouriens", -"Neuvy-Sautouriens", "Neuvy-sur-Barangeon", "Neuvy-sur-Loire", +"Neuvéglise-sur-Truyère", "Neuwiller-lès-Saverne", "Nevi'im", -"Néville-sur-Mer", -"névro-mimosie", -"névro-mimosies", "Nevy-lès-Dole", "Nevy-sur-Seille", -"Newcastle-under-Lyme", "New-Glasgois", +"New-York", +"New-Yorkais", +"New-Yorkaise", +"New-Yorkaises", +"Newcastle-under-Lyme", "Newton-in-Makerfield", "Newton-le-Willows", -"newton-mètre", -"newtons-mètres", -"New-York", -"new-yorkais", -"New-Yorkais", -"new-yorkaise", -"New-Yorkaise", -"new-yorkaises", -"New-Yorkaises", -"new-yorkisa", -"new-yorkisai", -"new-yorkisaient", -"new-yorkisais", -"new-yorkisait", -"new-yorkisâmes", -"new-yorkisant", -"new-yorkisas", -"new-yorkisasse", -"new-yorkisassent", -"new-yorkisasses", -"new-yorkisassiez", -"new-yorkisassions", -"new-yorkisât", -"new-yorkisâtes", -"new-yorkise", -"new-yorkisé", -"new-yorkisée", -"new-yorkisées", -"new-yorkisent", -"new-yorkiser", -"new-yorkisera", -"new-yorkiserai", -"new-yorkiseraient", -"new-yorkiserais", -"new-yorkiserait", -"new-yorkiseras", -"new-yorkisèrent", -"new-yorkiserez", -"new-yorkiseriez", -"new-yorkiserions", -"new-yorkiserons", -"new-yorkiseront", -"new-yorkises", -"new-yorkisés", -"new-yorkisez", -"new-yorkisiez", -"new-yorkisions", -"new-yorkisons", -"nez-en-cœur", -"Nézignan-l'Evêque", -"Nézignan-l'Évêque", -"nez-percé", -"ngaï-ngaï", -"ngaï-ngaïs", -"n-gone", -"n-gones", -"n-gramme", -"n-grammes", -"nian-nian", +"Ni-Skutterudites", "Nicey-sur-Aire", -"niche-crédence", -"nickel-ankérite", -"nickel-ankérites", -"nickel-magnésite", -"nickel-magnésites", -"nickel-skuttérudite", -"nickel-skuttérudites", "Nicolétain-du-Sud", -"nid-de-poule", -"Niederbronn-les-Bains", "Nieder-Hilbersheim", "Nieder-Olm", "Nieder-Wiesen", +"Niederbronn-les-Bains", "Niefern-Öschelbronn", "Niel-bij-As", "Niel-bij-Sint-Truiden", "Nielles-lès-Ardres", "Nielles-lès-Bléquin", "Nielles-lès-Calais", -"n-ième", -"n-ièmes", "Nieuil-l'Espoir", "Nieul-le-Dolent", -"Nieul-lès-Saintes", -"Nieulle-sur-Seudre", "Nieul-le-Virouil", -"Nieul-sur-l'Autise", +"Nieul-lès-Saintes", "Nieul-sur-Mer", +"Nieul-sur-l'Autise", +"Nieulle-sur-Seudre", "Nieuw-Amsterdam", "Nieuw-Annerveen", "Nieuw-Balinge", @@ -17184,16 +9585,12 @@ FR_BASE_EXCEPTIONS = [ "Nieuw-Buinen", "Nieuw-Dijk", "Nieuw-Dordrecht", -"Nieuwer-Amstel", -"Nieuwe-Tonge", "Nieuw-Ginneken", "Nieuw-Heeten", "Nieuw-Helvoet", -"Nieuwkerken-Waas", "Nieuw-Loosdrecht", "Nieuw-Milligen", "Nieuw-Namen", -"Nieuwolda-Oost", "Nieuw-Reemst", "Nieuw-Roden", "Nieuw-Scheemda", @@ -17203,102 +9600,30 @@ FR_BASE_EXCEPTIONS = [ "Nieuw-Vossemeer", "Nieuw-Weerdinge", "Nieuw-Wehl", +"Nieuwe-Tonge", +"Nieuwer-Amstel", +"Nieuwkerken-Waas", +"Nieuwolda-Oost", "Niger-Congo", -"nigéro-congolais", -"night-club", -"night-clubbing", -"night-clubs", "Nijni-Taguil", -"nilo-saharien", -"nilo-saharienne", -"nilo-sahariennes", -"nilo-sahariens", "Nil-Saint-Martin", "Nil-Saint-Vincent", "Nil-Saint-Vincent-Saint-Martin", -"ni-ni", -"nin-nin", "Niort-de-Sault", "Niort-la-Fontaine", -"nippo-américain", -"nippo-américaine", -"nippo-américaines", -"nippo-américains", -"nique-douille", -"nique-douilles", -"Ni-Skutterudites", "Nissan-lez-Enserune", "Nister-Möhrendorf", "Nistos-Haut-et-Bas", -"nitro-cellulose", -"nitro-celluloses", -"nitro-hydrochlorique", -"nitro-hydrochloriques", -"nitrotal-isopropyl", -"niuafo'ou", -"niuafo'ous", "Nivigne-et-Suran", -"nivo-glaciaire", -"nivo-glaciaires", "Nivolas-Vermelle", "Nivollet-Montgriffon", -"nivo-pluvial", "Nixéville-Blercourt", "Nizan-Gesse", "Nizy-le-Comte", "Nlle-Calédonie", -"Nlle-Écosse", "Nlle-Zélande", -"N-méthyla", -"N-méthylai", -"N-méthylaient", -"N-méthylais", -"N-méthylait", -"N-méthylâmes", -"N-méthylant", -"N-méthylas", -"N-méthylasse", -"N-méthylassent", -"N-méthylasses", -"N-méthylassiez", -"N-méthylassions", -"N-méthylât", -"N-méthylâtes", -"N-méthyle", -"N-méthylé", -"N-méthylée", -"N-méthylées", -"N-méthylent", -"N-méthyler", -"N-méthylera", -"N-méthylerai", -"N-méthyleraient", -"N-méthylerais", -"N-méthylerait", -"N-méthyleras", -"N-méthylèrent", -"N-méthylerez", -"N-méthyleriez", -"N-méthylerions", -"N-méthylerons", -"N-méthyleront", -"N-méthyles", -"N-méthylés", -"N-méthylez", -"N-méthyliez", -"N-méthylions", -"N-méthylons", -"N,N-dinitronitramide", -"n-octaèdre", -"n-octaèdres", +"Nlle-Écosse", "Nod-sur-Seine", -"Noël-Cerneux", -"Noé-les-Mallets", -"Noë-les-Mallets", -"nœud-nœud", -"nœuds-nœuds", -"Nœux-lès-Auxi", -"Nœux-les-Mines", "Nogent-en-Othe", "Nogent-l'Abbesse", "Nogent-l'Artaud", @@ -17315,65 +9640,44 @@ FR_BASE_EXCEPTIONS = [ "Nogent-sur-Oise", "Nogent-sur-Seine", "Nogent-sur-Vernisson", +"Nohant-Vic", "Nohant-en-Goût", "Nohant-en-Graçay", -"Nohant-Vic", "Noidans-le-Ferroux", "Noidans-lès-Vesoul", "Noidant-Chatenoy", "Noidant-le-Rocheux", -"noie-chien", "Noirmoutier-en-l'Île", "Noiron-sous-Gevrey", "Noiron-sur-Bèze", "Noiron-sur-Seine", -"noir-pie", -"noir-pioche", -"noir-pioches", -"noir-ployant", +"Noisy-Rudignon", +"Noisy-Rudignonais", +"Noisy-Rudignonaise", +"Noisy-Rudignonaises", "Noisy-le-Grand", "Noisy-le-Roi", "Noisy-le-Sec", -"Noisy-Rudignon", -"noisy-rudignonais", -"Noisy-Rudignonais", -"noisy-rudignonaise", -"Noisy-Rudignonaise", -"noisy-rudignonaises", -"Noisy-Rudignonaises", "Noisy-sur-Ecole", -"Noisy-sur-École", "Noisy-sur-Oise", +"Noisy-sur-École", "Nojals-et-Clotte", "Nojeon-en-Vexin", "Nojeon-le-Sec", -"no-kill", -"no-kills", -"noli-me-tangere", -"nonante-cinq", -"nonante-deux", -"nonante-et-un", -"nonante-huit", -"nonante-neuf", -"nonante-quatre", -"nonante-sept", -"nonante-six", -"nonante-trois", "Nonant-le-Pin", "Noncourt-sur-le-Rongeant", "Nonette-Orsonnette", "Nonsard-Lamarche", "Nonvilliers-Grandhoux", -"Noorder-Koggenland", "Noord-Polsbroek", "Noord-Scharwoude", "Noord-Sleen", "Noord-Spierdijk", "Noord-Stroe", "Noord-Waddinxveen", +"Noorder-Koggenland", "Noordwijk-Binnen", "Noordwolde-Zuid", -"no-poo", "Norges-la-Ville", "Noron-l'Abbaye", "Noron-la-Poterie", @@ -17384,56 +9688,69 @@ FR_BASE_EXCEPTIONS = [ "Norrey-en-Auge", "Norrey-en-Bessin", "Norroy-le-Sec", -"Norroy-lès-Pont-à-Mousson", "Norroy-le-Veneur", -"Nörten-Hardenberg", +"Norroy-lès-Pont-à-Mousson", "Nort-Leulinghem", -"nort-leulinghemois", "Nort-Leulinghemois", -"nort-leulinghemoise", "Nort-Leulinghemoise", -"nort-leulinghemoises", "Nort-Leulinghemoises", "Nort-sur-Erdre", "Norwich-terrier", "Nossage-et-Bénévent", +"Notre-Dame-d'Aliermont", +"Notre-Dame-d'Allençon", +"Notre-Dame-d'Estrées-Corbon", +"Notre-Dame-d'Oé", +"Notre-Dame-d'Épine", +"Notre-Dame-de-Bellecombe", +"Notre-Dame-de-Bliquetuit", +"Notre-Dame-de-Boisset", +"Notre-Dame-de-Bondeville", +"Notre-Dame-de-Cenilly", +"Notre-Dame-de-Commiers", +"Notre-Dame-de-Livaye", +"Notre-Dame-de-Livoye", +"Notre-Dame-de-Londres", +"Notre-Dame-de-Monts", +"Notre-Dame-de-Mésage", +"Notre-Dame-de-Riez", +"Notre-Dame-de-Sanilhac", +"Notre-Dame-de-Vaulx", +"Notre-Dame-de-l'Isle", +"Notre-Dame-de-l'Osier", +"Notre-Dame-de-la-Rouvière", +"Notre-Dame-des-Landes", +"Notre-Dame-des-Millières", +"Notre-Dame-du-Bec", +"Notre-Dame-du-Cruet", +"Notre-Dame-du-Hamel", +"Notre-Dame-du-Parc", +"Notre-Dame-du-Pré", +"Notre-Dame-du-Pé", "Nouaillé-Maupertuis", "Nouan-le-Fuzelier", -"Nouans-les-Fontaines", "Nouan-sur-Loire", +"Nouans-les-Fontaines", "Noues-de-Sienne", "Nourard-le-Franc", -"nous-même", -"nous-mêmes", +"Nousseviller-Saint-Nabor", "Nousseviller-lès-Bitche", "Nousseviller-lès-Puttelange", -"Nousseviller-Saint-Nabor", "Nouveau-Brunswick", "Nouveau-Connecticut", "Nouveau-Continent", "Nouveau-Cornouaille", "Nouveau-Cornouailles", "Nouveau-Cornwall", -"nouveau-gallois", "Nouveau-Hanovre", "Nouveau-Léon", "Nouveau-Mexique", "Nouveau-Monde", -"nouveau-né", -"nouveau-née", -"nouveau-nées", -"nouveau-nés", "Nouveau-Norfolk", "Nouveau-Santander", "Nouveau-Shetland", -"nouveau-venu", -"nouveaux-nés", "Nouveaux-Pays-Bas", -"nouveaux-venus", "Nouvel-Âge", -"nouvel-âgeuse", -"nouvel-âgeuses", -"nouvel-âgeux", "Nouvelle-Albion", "Nouvelle-Amsterdam", "Nouvelle-Andalousie", @@ -17444,42 +9761,38 @@ FR_BASE_EXCEPTIONS = [ "Nouvelle-Cornouaille", "Nouvelle-Cornouailles", "Nouvelle-Cythère", -"Nouvelle-Écosse", "Nouvelle-Eglise", -"Nouvelle-Église", "Nouvelle-Espagne", "Nouvelle-France", "Nouvelle-Galles", -"Nouvelle-Géorgie", "Nouvelle-Grenade", "Nouvelle-Guinée", +"Nouvelle-Géorgie", "Nouvelle-Hanovre", "Nouvelle-Hollande", "Nouvelle-Irlande", -"nouvelle-née", -"Nouvelle-Néerlande", "Nouvelle-Norfolk", +"Nouvelle-Néerlande", "Nouvelle-Orléans", "Nouvelle-Poméranie", -"Nouvelles-Hébrides", "Nouvelle-Sibérie", -"nouvelles-nées", -"nouvelles-venues", -"nouvelle-venue", "Nouvelle-Zamble", -"Nouvelle-Zélande", "Nouvelle-Zemble", +"Nouvelle-Zélande", +"Nouvelle-Écosse", +"Nouvelle-Église", +"Nouvelles-Hébrides", "Nouvion-et-Catillon", "Nouvion-le-Comte", "Nouvion-le-Vineux", "Nouvion-sur-Meuse", "Nouvron-Vingré", -"Novéant-sur-Moselle", "Noviant-aux-Prés", "Noville-les-Bois", "Noville-sur-Mehaigne", "Novion-Porcien", "Novy-Chevrières", +"Novéant-sur-Moselle", "Noyal-Châtillon-sur-Seiche", "Noyal-Muzillac", "Noyal-Pontivy", @@ -17491,20 +9804,19 @@ FR_BASE_EXCEPTIONS = [ "Noyant-et-Aconin", "Noyant-la-Gravoyère", "Noyant-la-Plaine", -"noyé-d'eau", -"Noyelles-en-Chaussée", +"Noyelle-Vion", "Noyelles-Godault", +"Noyelles-en-Chaussée", "Noyelles-lès-Humières", "Noyelles-lès-Seclin", "Noyelles-lès-Vermelles", "Noyelles-sous-Bellonne", "Noyelles-sous-Lens", "Noyelles-sur-Escaut", -"Noyelles-sur-l'Escaut", "Noyelles-sur-Mer", "Noyelles-sur-Sambre", "Noyelles-sur-Selle", -"Noyelle-Vion", +"Noyelles-sur-l'Escaut", "Noyen-sur-Sarthe", "Noyen-sur-Seine", "Noyers-Auzécourt", @@ -17512,113 +9824,49 @@ FR_BASE_EXCEPTIONS = [ "Noyers-Missy", "Noyers-Pont-Maugis", "Noyers-Saint-Martin", +"Noyers-Thélonne", "Noyers-sur-Cher", "Noyers-sur-Jabron", -"Noyers-Thélonne", -"n-polytope", -"n-polytopes", -"n-simplexe", -"n-simplexes", -"n-sphère", -"n-sphères", -"n'srani", -"N'Tcham", +"Noé-les-Mallets", +"Noë-les-Mallets", +"Noël-Cerneux", "Nuaillé-d'Aunis", "Nuaillé-sur-Boutonne", "Nueil-les-Aubiers", "Nueil-sous-Faye", "Nueil-sous-les-Aubiers", "Nueil-sur-Layon", -"nue-propriétaire", -"nue-propriété", -"nuer-dinka", -"nues-propriétaires", -"nues-propriétés", "Nuillé-le-Jalais", "Nuillé-sur-Ouette", "Nuillé-sur-Vicoin", "Nuisement-aux-Bois", "Nuisement-sur-Coole", -"nuit-deboutiste", -"nuit-deboutistes", "Nuits-Saint-Georges", "Nuka-Hiva", "Nuku-Hiva", "Nuncq-Hautecôte", -"nuoc-mam", -"nuoc-mâm", -"nu-pied", -"nu-pieds", -"n-uple", -"n-uples", -"n-uplet", -"n-uplets", -"nu-propriétaire", "Nuret-le-Ferron", "Nurieux-Volognat", -"nus-propriétaires", -"nu-tête", "Nuthe-Urstromtal", -"N.-W.", -"Oberdorf-Spachbach", -"Oberehe-Stroheich", -"Ober-Flörsheim", -"Oberhausen-Rheinhausen", -"Ober-Hilbersheim", -"Oberhoffen-lès-Wissembourg", -"Oberhoffen-sur-Moder", -"Oberhonnefeld-Gierend", -"Obermaßfeld-Grimmenthal", -"Obermodern-Zutzendorf", -"Ober-Mörlen", -"Obernheim-Kirchenarnbach", -"Ober-Olm", -"Ober-Ramstadt", -"Oberweiler-Tiefenbach", -"Oberwil-Lieli", -"occipito-atloïdien", -"occipito-atloïdienne", -"occipito-atloïdiennes", -"occipito-atloïdiens", -"occipito-axoïdien", -"occipito-axoïdienne", -"occipito-axoïdiennes", -"occipito-axoïdiens", -"occipito-cotyloïdien", -"occipito-cotyloïdienne", -"occipito-cotyloïdiennes", -"occipito-cotyloïdiens", -"occipito-frontal", -"occipito-méningien", -"occipito-pariétal", -"occipito-pétreuse", -"occipito-pétreuses", -"occipito-pétreux", -"occipito-sacré", -"occipito-sacro-iliaque", -"occitano-roman", -"octante-deux", -"octante-et-un", -"octante-neuf", -"Octeville-l'Avenel", -"Octeville-la-Venelle", -"Octeville-sur-Mer", -"octo-core", -"octo-cores", -"octo-rotor", -"octo-rotors", -"oculo-motricité", -"oculo-motricités", -"oculo-musculaire", -"oculo-musculaires", -"oculo-zygomatique", -"Odeillo-Via", +"Nœux-les-Mines", +"Nœux-lès-Auxi", +"Nâves-Parmelan", +"Néant-sur-Yvel", +"Néons-sur-Creuse", +"Néris-les-Bains", +"Néronde-sur-Dore", +"Néville-sur-Mer", +"Nézignan-l'Evêque", +"Nézignan-l'Évêque", +"Nörten-Hardenberg", +"Nœux-les-Mines", +"Nœux-lès-Auxi", +"O-desvenlafaxine", "O-déméthyla", "O-déméthylai", "O-déméthylaient", "O-déméthylais", "O-déméthylait", -"O-déméthylâmes", "O-déméthylant", "O-déméthylas", "O-déméthylasse", @@ -17626,12 +9874,7 @@ FR_BASE_EXCEPTIONS = [ "O-déméthylasses", "O-déméthylassiez", "O-déméthylassions", -"O-déméthylât", -"O-déméthylâtes", "O-déméthyle", -"O-déméthylé", -"O-déméthylée", -"O-déméthylées", "O-déméthylent", "O-déméthyler", "O-déméthylera", @@ -17640,135 +9883,29 @@ FR_BASE_EXCEPTIONS = [ "O-déméthylerais", "O-déméthylerait", "O-déméthyleras", -"O-déméthylèrent", "O-déméthylerez", "O-déméthyleriez", "O-déméthylerions", "O-déméthylerons", "O-déméthyleront", "O-déméthyles", -"O-déméthylés", "O-déméthylez", "O-déméthyliez", "O-déméthylions", "O-déméthylons", -"Oder-Spree", -"O-desvenlafaxine", -"odonto-stomatologie", -"Oebisfelde-Weferlingen", -"oeil-de-boeuf", -"œil-de-bœuf", -"oeil-de-chat", -"œil-de-chat", -"oeil-de-lièvre", -"oeil-de-paon", -"oeil-de-perdrix", -"œil-de-perdrix", -"oeil-de-pie", -"œil-de-pie", -"oeil-de-serpent", -"œil-de-serpent", -"oeil-de-tigre", -"œil-de-tigre", -"oeil-du-soleil", -"œil-du-soleil", -"oeils-de-boeuf", -"œils-de-bœuf", -"oeils-de-chat", -"oeils-de-lièvre", -"oeils-de-paon", -"oeils-de-perdrix", -"oeils-de-pie", -"œils-de-pie", -"oeils-de-serpent", -"œils-de-serpent", -"oeils-de-tigre", -"œils-de-tigre", -"Oer-Erkenschwick", -"oesophago-gastro-duodénoscopie", -"œsophago-gastro-duodénoscopie", -"oesophago-gastro-duodénoscopies", -"œsophago-gastro-duodénoscopies", -"Oestrich-Winkel", -"œuf-coque", -"Œuf-en-Ternois", -"œufs-coque", -"Offenbach-Hundheim", -"Offenbach-sur-le-Main", -"off-market", -"off-shore", -"Ogenne-Camptort", -"Ogeu-les-Bains", -"ogivo-cylindrique", -"Ogooué-Maritime", -"Ogy-Montoy-Flanville", -"ohm-mètre", -"ohms-mètres", -"oie-cygne", -"Oignies-en-Thiérache", -"Oigny-en-Valois", -"Oinville-Saint-Liphard", -"Oinville-sous-Auneau", -"Oinville-sur-Montcient", -"oiseau-chameau", -"oiseau-cloche", -"oiseau-éléphant", -"oiseau-lyre", -"oiseau-mouche", -"oiseau-papillon", -"oiseau-tonnerre", -"oiseau-trompette", -"oiseaux-chameaux", -"oiseaux-cloches", -"oiseaux-lyres", -"oiseaux-mouches", -"oiseaux-papillons", -"oiseaux-tonnerres", -"oiseaux-trompettes", -"Oiselay-et-Grachaux", -"Oisseau-le-Petit", -"Oisy-le-Verger", -"Ojos-Albos", -"Olbia-Tempio", -"Ölbronn-Dürrn", -"old-ice", -"old-ices", -"Oléac-Debat", -"Oléac-Dessus", -"oléo-calcaire", -"oléo-calcaires", -"olé-olé", -"oligo-élément", -"oligo-éléments", -"Olizy-Primat", -"Olizy-sur-Chiers", -"olla-podrida", -"Olloy-sur-Viroin", -"Olmeta-di-Capocorso", -"Olmeta-di-Tuda", -"Olmet-et-Villecun", -"Olmi-Cappella", -"Olonne-sur-Mer", -"Oloron-Sainte-Marie", -"Oloron-Sainte-Marie", -"Ols-et-Rinhodes", -"Olst-Wijhe", -"omaha-ponca", -"omaha-poncas", -"omble-chevalier", -"ombre-chevalier", -"Ombret-Rawsa", -"ombro-thermique", -"ombro-thermiques", -"oméga-3", -"oméga-6", -"oméga-9", +"O-déméthylâmes", +"O-déméthylât", +"O-déméthylâtes", +"O-déméthylèrent", +"O-déméthylé", +"O-déméthylée", +"O-déméthylées", +"O-déméthylés", "O-méthyla", "O-méthylai", "O-méthylaient", "O-méthylais", "O-méthylait", -"O-méthylâmes", "O-méthylant", "O-méthylas", "O-méthylasse", @@ -17776,12 +9913,7 @@ FR_BASE_EXCEPTIONS = [ "O-méthylasses", "O-méthylassiez", "O-méthylassions", -"O-méthylât", -"O-méthylâtes", "O-méthyle", -"O-méthylé", -"O-méthylée", -"O-méthylées", "O-méthylent", "O-méthyler", "O-méthylera", @@ -17790,121 +9922,123 @@ FR_BASE_EXCEPTIONS = [ "O-méthylerais", "O-méthylerait", "O-méthyleras", -"O-méthylèrent", "O-méthylerez", "O-méthyleriez", "O-méthylerions", "O-méthylerons", "O-méthyleront", "O-méthyles", -"O-méthylés", "O-méthylez", "O-méthyliez", "O-méthylions", "O-méthylons", +"O-méthylâmes", +"O-méthylât", +"O-méthylâtes", +"O-méthylèrent", +"O-méthylé", +"O-méthylée", +"O-méthylées", +"O-méthylés", +"Ober-Flörsheim", +"Ober-Hilbersheim", +"Ober-Mörlen", +"Ober-Olm", +"Ober-Ramstadt", +"Oberdorf-Spachbach", +"Oberehe-Stroheich", +"Oberhausen-Rheinhausen", +"Oberhoffen-lès-Wissembourg", +"Oberhoffen-sur-Moder", +"Oberhonnefeld-Gierend", +"Obermaßfeld-Grimmenthal", +"Obermodern-Zutzendorf", +"Obernheim-Kirchenarnbach", +"Oberweiler-Tiefenbach", +"Oberwil-Lieli", +"Octeville-l'Avenel", +"Octeville-la-Venelle", +"Octeville-sur-Mer", +"Odeillo-Via", +"Oder-Spree", +"Oebisfelde-Weferlingen", +"Oer-Erkenschwick", +"Oestrich-Winkel", +"Offenbach-Hundheim", +"Offenbach-sur-le-Main", +"Ogenne-Camptort", +"Ogeu-les-Bains", +"Ogooué-Maritime", +"Ogy-Montoy-Flanville", +"Oignies-en-Thiérache", +"Oigny-en-Valois", +"Oinville-Saint-Liphard", +"Oinville-sous-Auneau", +"Oinville-sur-Montcient", +"Oiselay-et-Grachaux", +"Oisseau-le-Petit", +"Oisy-le-Verger", +"Ojos-Albos", +"Olbia-Tempio", +"Olizy-Primat", +"Olizy-sur-Chiers", +"Olloy-sur-Viroin", +"Olmet-et-Villecun", +"Olmeta-di-Capocorso", +"Olmeta-di-Tuda", +"Olmi-Cappella", +"Olonne-sur-Mer", +"Oloron-Sainte-Marie", +"Ols-et-Rinhodes", +"Olst-Wijhe", +"Oléac-Debat", +"Oléac-Dessus", +"Ombret-Rawsa", "Omonville-la-Petite", "Omonville-la-Rogue", -"omphalo-mésentérique", -"omphalo-mésentériques", -"omphalo-phlébite", -"omphalo-phlébites", "Oncy-sur-Ecole", "Oncy-sur-École", -"on-dit", "Ondreville-sur-Essonne", -"one-man-show", -"one-shot", -"Onesse-et-Laharie", "Onesse-Laharie", -"one-step", -"one-steps", +"Onesse-et-Laharie", "Onet-le-Château", -"one-woman-show", "Ons-en-Bray", "Onze-Lieve-Vrouw-Waver", "Oost-Barendrecht", "Oost-Cappel", -"oost-cappelois", "Oost-Cappelois", -"oost-cappeloise", "Oost-Cappeloise", -"oost-cappeloises", "Oost-Cappeloises", -"Ooster-Dalfsen", -"Oosterzee-Buren", "Oost-Graftdijk", "Oost-Maarland", "Oost-Souburg", "Oost-Vlieland", -"opal-AN", -"open-source", -"open-space", -"open-spaces", -"opéra-comique", -"Opéra-Comique", -"opéras-comiques", +"Ooster-Dalfsen", +"Oosterzee-Buren", "Ophain-Bois-Seigneur-Isaac", "Opoul-Périllos", -"opt-in", -"opto-strié", -"opt-out", +"Opéra-Comique", +"Or-Blanois", "Oradour-Fanais", "Oradour-Saint-Genest", "Oradour-sur-Glane", "Oradour-sur-Vayres", -"orang-outan", -"orang-outang", -"orangs-outangs", -"orangs-outans", "Oranienbaum-Wörlitz", "Orbais-l'Abbaye", "Orbigny-au-Mont", "Orbigny-au-Val", -"orbito-nasal", -"orbito-palpébral", -"Or-Blanois", "Orchamps-Vennes", "Ordan-Larroque", -"Orée-d'Anjou", -"oreille-d'abbé", -"oreille-d'âne", -"oreille-de-lièvre", -"oreille-de-loup", -"oreille-de-mer", -"oreille-de-souris", -"oreille-d'ours", -"oreilles-d'âne", -"oreilles-de-mer", -"oreilles-de-souris", -"oreilles-d'ours", -"organo-calcaire", -"organo-calcaires", -"organo-chloré", -"organo-chlorée", -"organo-chlorées", -"organo-chlorés", -"organo-halogéné", -"organo-halogénée", -"organo-halogénées", -"organo-halogénés", -"organo-phosphoré", -"organo-phosphorée", -"organo-phosphorées", -"organo-phosphorés", "Orgeans-Blanchefontaine", -"Orgères-en-Beauce", -"Orgères-la-Roche", "Orgnac-l'Aven", "Orgnac-sur-Vézère", -"orienté-objet", -"orienteur-marqueur", +"Orgères-en-Beauce", +"Orgères-la-Roche", +"Origny-Sainte-Benoite", "Origny-en-Thiérache", "Origny-le-Butin", "Origny-le-Roux", "Origny-le-Sec", -"Origny-Sainte-Benoite", -"o-ring", -"o-rings", "Oriol-en-Royans", "Oris-en-Rattier", "Orliac-de-Bar", @@ -17913,58 +10047,44 @@ FR_BASE_EXCEPTIONS = [ "Ormesson-sur-Marne", "Ormont-Dessous", "Ormont-Dessus", +"Ormoy-Villers", "Ormoy-la-Rivière", "Ormoy-le-Davien", "Ormoy-lès-Sexfontaines", "Ormoy-sur-Aube", -"Ormoy-Villers", "Ornolac-Ussat-les-Bains", "Oroz-Betelu", "Orp-Jauche", -"orp-jauchois", "Orp-Jauchois", "Orp-Jauchoise", "Orp-le-Grand", "Orry-la-Ville", "Orsingen-Nenzingen", "Orsmaal-Gussenhoven", -"or-sol", -"ortho-sympathique", -"ortho-sympathiques", "Orthoux-Sérignac-Quilhan", "Orveau-Bellesauve", "Orvillers-Sorel", "Orvilliers-Saint-Julien", +"Orée d'Anjou", +"Orée-d'Anjou", +"Os-Marsillon", "Osann-Monzel", "Osly-Courtil", -"Os-Marsillon", "Osmoy-Saint-Valery", "Osne-le-Val", "Ossas-Suhare", -"ossau-iraty", -"ossau-iratys", "Osse-en-Aspe", "Osselle-Routelle", "Osserain-Rivareyte", -"Ossétie-du-Nord-Alanie", "Ossey-les-Trois-Maisons", "Ossun-ez-Angles", +"Ossétie-du-Nord-Alanie", "Ostabat-Asme", -"ostéo-arthrite", -"ostéo-arthrites", -"Osterholz-Scharmbeck", "Oster-Ohrstedt", +"Osterholz-Scharmbeck", "Osthausen-Wülfershausen", -"ôte-agrafes", -"oto-rhino", -"oto-rhino-laryngologie", -"oto-rhino-laryngologies", -"oto-rhino-laryngologiste", -"oto-rhino-laryngologistes", -"oto-rhinos", "Ottendorf-Okrilla", "Ottignies-Louvain-la-Neuve", -"ouaf-ouaf", "Oud-Aa", "Oud-Alblas", "Oud-Annerveen", @@ -17973,9 +10093,6 @@ FR_BASE_EXCEPTIONS = [ "Oud-Dijk", "Oud-Drimmelen", "Oud-Empel", -"Ouder-Amstel", -"Ouderkerk-sur-l'Amstel", -"Oude-Tonge", "Oud-Gastel", "Oud-Heverlee", "Oud-Kamerik", @@ -17991,25 +10108,20 @@ FR_BASE_EXCEPTIONS = [ "Oud-Vroenhoven", "Oud-Wulven", "Oud-Zuilen", -"ouèche-ouèche", -"ouèches-ouèches", +"Oude-Tonge", +"Ouder-Amstel", +"Ouderkerk-sur-l'Amstel", "Ougney-Douvot", -"oui-da", -"ouï-dire", +"Oui-Oui", "Ouilly-du-Houley", "Ouilly-le-Basset", "Ouilly-le-Tesson", "Ouilly-le-Vicomte", -"oui-non-bof", -"Oui-Oui", -"ouïr-dire", "Oulan-Bator", "Oulches-la-Vallée-Foulon", "Oulchy-la-Ville", "Oulchy-le-Château", "Oulens-sous-Échallens", -"ouralo-altaïque", -"ouralo-altaïques", "Ourcel-Maison", "Ourches-sur-Meuse", "Ourdis-Cotdoussan", @@ -18018,22 +10130,17 @@ FR_BASE_EXCEPTIONS = [ "Ouroux-en-Morvan", "Ouroux-sous-le-Bois-Sainte-Marie", "Ouroux-sur-Saône", -"Oursel-Maison", -"ours-garou", -"ours-garous", "Ours-Mons", +"Oursel-Maison", "Ourville-en-Caux", -"Ousse-et-Suzan", "Ousse-Suzan", +"Ousse-et-Suzan", "Ousson-sur-Loire", "Oussoy-en-Gâtinais", "Oust-Marest", "Ouve-Wirquin", -"ouve-wirquinois", "Ouve-Wirquinois", -"ouve-wirquinoise", "Ouve-Wirquinoise", -"ouve-wirquinoises", "Ouve-Wirquinoises", "Ouville-l'Abbaye", "Ouville-la-Bien-Tournée", @@ -18050,26 +10157,11 @@ FR_BASE_EXCEPTIONS = [ "Ouzoun-Ada", "Over-Diemen", "Ovillers-la-Boisselle", -"ovo-lacto-végétarisme", -"ovo-lacto-végétarismes", -"ovo-urinaire", -"ovo-végétarisme", -"ovo-végétarismes", -"oxidéméton-méthyl", -"oxo-biodégradable", -"oxo-biodégradables", -"oxo-dégradable", -"oxo-dégradables", -"oxydéméton-méthyl", -"oxydo-réduction", -"oxydo-réductions", -"oxy-iodure", -"oxy-iodures", -"Oye-et-Pallet", -"Oye-Plage", "Oy-Mittelberg", -"Oyón-Oion", +"Oye-Plage", +"Oye-et-Pallet", "Oytier-Saint-Oblas", +"Oyón-Oion", "Oza-Cesuras", "Ozenx-Montestrucq", "Ozoir-la-Ferrière", @@ -18077,18 +10169,14 @@ FR_BASE_EXCEPTIONS = [ "Ozouer-le-Repos", "Ozouer-le-Voulgis", "Ozouër-le-Voulgis", -"pa'anga", -"p-acétylaminophénol", -"package-deal", -"package-deals", -"pack-ice", -"pack-ices", +"P-ATA", +"P-DG", +"P-frame", +"P.-D.G.", +"PPD-T", +"Pa-O", "Pacy-sur-Armançon", "Pacy-sur-Eure", -"p-adique", -"p-adiques", -"pagano-chrétien", -"page-turner", "Pagney-derrière-Barine", "Pagny-la-Blanche-Côte", "Pagny-la-Ville", @@ -18096,163 +10184,37 @@ FR_BASE_EXCEPTIONS = [ "Pagny-lès-Goin", "Pagny-sur-Meuse", "Pagny-sur-Moselle", -"paille-en-cul", -"paille-en-queue", -"pailles-en-cul", -"pailles-en-queue", -"pail-mail", -"pain-beurre", -"pain-d'épicier", -"pain-d'épicière", -"pain-d'épicières", -"pain-d'épiciers", -"pain-de-pourceau", -"pains-de-pourceau", -"pair-à-pair", "Pair-et-Grandrupt", -"pair-programma", -"pair-programmai", -"pair-programmaient", -"pair-programmais", -"pair-programmait", -"pair-programmâmes", -"pair-programmant", -"pair-programmas", -"pair-programmasse", -"pair-programmassent", -"pair-programmasses", -"pair-programmassiez", -"pair-programmassions", -"pair-programmât", -"pair-programmâtes", -"pair-programme", -"pair-programmé", -"pair-programment", -"pair-programmer", -"pair-programmera", -"pair-programmerai", -"pair-programmeraient", -"pair-programmerais", -"pair-programmerait", -"pair-programmeras", -"pair-programmèrent", -"pair-programmerez", -"pair-programmeriez", -"pair-programmerions", -"pair-programmerons", -"pair-programmeront", -"pair-programmes", -"pair-programmez", -"pair-programmiez", -"pair-programmions", -"pair-programmons", "Paisy-Cosdon", +"Paizay-Naudouin-Embourie", "Paizay-le-Chapt", "Paizay-le-Sec", "Paizay-le-Tort", -"Paizay-Naudouin-Embourie", "Palais-Bourbon", "Palatinat-Sud-Ouest", -"palato-labial", -"palato-labiale", -"palato-pharyngien", -"palato-pharyngite", -"palato-pharyngites", -"palato-salpingien", -"palato-staphylin", -"palato-staphylins", "Palau-de-Cerdagne", "Palau-del-Vidre", "Palau-sator", "Palau-saverdera", "Palavas-les-Flots", -"paléo-continental", -"paléo-lac", -"paléo-lacs", -"paléo-reconstruction", -"paléo-reconstructions", -"pal-fer", -"palladico-potassique", "Palluau-sur-Indre", -"palmier-chanvre", -"palmier-dattier", -"palmiers-chanvre", -"palmiers-dattiers", -"palpe-mâchoire", -"palu'e", -"palu'es", -"pama-nyungan", -"panchen-lama", -"pancréatico-duodénal", +"Palmas d'Aveyron", "Pancy-Courtecon", -"pan-européen", -"pan-européenne", -"pan-européennes", -"pan-européens", -"panier-repas", -"paniers-repas", -"pan-lucanisme", -"pan-mandingue", -"pan-mandingues", -"panpan-cucul", "Panschwitz-Kuckau", -"panthère-garou", -"panthères-garous", "Pant'ruche", -"Pa-O", -"papa-gâteau", -"papas-gâteaux", -"papier-caillou-ciseaux", -"papier-calque", -"papier-cul", -"papier-filtre", -"papier-monnaie", -"papiers-calque", "Papouasie-Nouvelle-Guinée", -"papy-boom", -"papy-boomer", -"papy-boomers", -"papy-boomeur", -"papy-boomeurs", -"paquet-cadeau", -"paquets-cadeaux", -"para-acétyl-amino-phénol", -"parachute-frein", -"parachutes-freins", -"para-continental", -"para-dichlorobenzène", -"para-légal", -"para-légale", -"para-légales", -"para-légaux", -"parathion-éthyl", -"parathion-méthyl", "Paray-Douaville", +"Paray-Vieille-Poste", "Paray-le-Frésil", "Paray-le-Monial", "Paray-sous-Briailles", -"Paray-Vieille-Poste", -"Parçay-les-Pins", -"Parçay-Meslay", -"Parçay-sur-Vienne", "Parc-d'Anxtot", -"parc-d'anxtotais", "Parc-d'Anxtotais", -"parc-d'anxtotaise", "Parc-d'Anxtotaise", -"parc-d'anxtotaises", "Parc-d'Anxtotaises", -"Parcé-sur-Sarthe", -"par-cœur", "Parcoul-Chenaud", "Parcy-et-Tigny", -"par-dehors", -"par-delà", -"par-derrière", -"par-dessous", -"par-dessus", -"par-devant", -"par-devers", +"Parcé-sur-Sarthe", "Pardies-Piétat", "Parentis-en-Born", "Parey-Saint-Césaire", @@ -18260,306 +10222,98 @@ FR_BASE_EXCEPTIONS = [ "Parfouru-l'Éclin", "Parfouru-sur-Odon", "Pargny-Filain", +"Pargny-Resson", "Pargny-la-Dhuys", "Pargny-les-Bois", "Pargny-lès-Reims", -"Pargny-Resson", "Pargny-sous-Mureau", "Pargny-sur-Saulx", -"Parigné-le-Pôlin", -"Parigné-l'Evêque", -"Parigné-l'Évêque", -"Parigné-sur-Braye", "Parigny-la-Rose", "Parigny-les-Vaux", +"Parigné-l'Evêque", +"Parigné-l'Évêque", +"Parigné-le-Pôlin", +"Parigné-sur-Braye", "Paris-Brest", "Paris-l'Hôpital", -"parking-relais", -"parler-pour-ne-rien-dire", -"Parné-sur-Roc", "Parnoy-en-Bassigny", -"parotido-auriculaire", -"parotido-auriculaires", +"Parné-sur-Roc", "Paroy-en-Othe", "Paroy-sur-Saulx", "Paroy-sur-Tholon", -"Parsac-Rimondeix", "Pars-lès-Chavanges", "Pars-lès-Romilly", +"Parsac-Rimondeix", "Parthenay-de-Bretagne", -"participation-pari", -"particule-dieu", -"particules-dieu", -"parti-pris", -"parva-pétricien", "Parva-Pétricien", -"parva-pétricienne", "Parva-Pétricienne", -"parva-pétriciennes", "Parva-Pétriciennes", -"parva-pétriciens", "Parva-Pétriciens", "Parves-et-Nattages", "Parvillers-le-Quesnoy", -"pas-à-pas", -"pascal-seconde", -"pascals-secondes", -"pas-d'âne", +"Parçay-Meslay", +"Parçay-les-Pins", +"Parçay-sur-Vienne", "Pas-de-Calais", "Pas-de-Jeu", -"pas-de-porte", "Pas-en-Artois", -"paso-doble", -"paso-dobles", "Passavant-en-Argonne", "Passavant-la-Rochère", "Passavant-sur-Layon", -"passif-agressif", -"passifs-agressifs", -"passing-shot", -"passing-shots", -"Passy-en-Valois", "Passy-Grigny", +"Passy-en-Valois", "Passy-les-Tours", "Passy-sur-Marne", "Passy-sur-Seine", -"P-ATA", -"pâtissier-chocolatier", -"Pätow-Steegen", -"patronnière-gradeuse", -"patronnières-gradeuses", -"patronnier-gradeur", -"patronniers-gradeurs", -"patte-de-lièvre", -"patte-d'oie", -"patte-pelu", -"patte-pelus", -"pattes-de-lièvre", -"pattes-d'oie", -"pauci-relationnel", -"pauci-relationnelle", -"pauci-relationnelles", -"pauci-relationnels", -"pauci-spécifique", -"pauci-spécifiques", -"Paulhac-en-Margeride", "Paul-Olivier", -"pause-café", -"pause-carrière", -"pause-santé", -"pauses-café", -"pauses-carrière", -"pauses-santé", +"Paulhac-en-Margeride", "Paussac-et-Saint-Vivien", "Pautaines-Augeville", -"payé-emporté", -"pay-per-view", "Payra-sur-l'Hers", -"Payré-sur-Vendée", "Payrin-Augmontel", "Payros-Cazautets", -"pays-bas", +"Payré-sur-Vendée", "Pays-Bas", "Pays-d'Altenbourg", +"Pays-d'Enhaut", "Pays-de-Berchtesgaden", "Pays-de-Jerichow", -"Pays-d'Enhaut", "Pays-de-Nuremberg", -"pay-to-win", "Payzac-de-Lanouaille", -"pc-banking", -"P-DG", -"P.-D.G.", -"p.-ê.", -"peau-bleue", -"peau-de-chienna", -"peau-de-chiennai", -"peau-de-chiennaient", -"peau-de-chiennais", -"peau-de-chiennait", -"peau-de-chiennâmes", -"peau-de-chiennant", -"peau-de-chiennas", -"peau-de-chiennasse", -"peau-de-chiennassent", -"peau-de-chiennasses", -"peau-de-chiennassiez", -"peau-de-chiennassions", -"peau-de-chiennât", -"peau-de-chiennâtes", -"peau-de-chienne", -"peau-de-chienné", -"peau-de-chiennée", -"peau-de-chiennées", -"peau-de-chiennent", -"peau-de-chienner", -"peau-de-chiennera", -"peau-de-chiennerai", -"peau-de-chienneraient", -"peau-de-chiennerais", -"peau-de-chiennerait", -"peau-de-chienneras", -"peau-de-chiennèrent", -"peau-de-chiennerez", -"peau-de-chienneriez", -"peau-de-chiennerions", -"peau-de-chiennerons", -"peau-de-chienneront", -"peau-de-chiennes", -"peau-de-chiennés", -"peau-de-chiennez", -"peau-de-chienniez", -"peau-de-chiennions", -"peau-de-chiennons", -"peau-rouge", "Peau-Rouge", "Peau-Verte", -"peaux-rouges", "Peaux-Rouges", "Peaux-Vertes", -"Pécharic-et-le-Py", -"pêche-bernard", -"pêche-bernards", "Pech-Luna", -"pédal'eau", -"pédicure-podologue", -"pédicures-podologues", "Pedro-Rodríguez", -"peer-to-peer", -"Pégairolles-de-Buèges", -"Pégairolles-de-l'Escalette", -"peigne-cul", -"peigne-culs", -"peigne-zizi", -"peine-à-jouir", -"peis-coua", "Peisey-Nancroix", -"pele-ata", "Pel-et-Der", -"pelle-à-cul", -"pelle-pioche", -"pelles-à-cul", -"pelles-bêches", -"pelles-pioches", "Pellouailles-les-Vignes", -"pelure-d'oignon", -"pelvi-crural", -"pelvi-trochantérien", -"pelvi-trochantérienne", -"pelvi-trochantériennes", -"pelvi-trochantériens", -"Peñacerrada-Urizaharra", -"Peñarroya-Pueblonuevo", -"pencak-silat", -"pénicillino-résistance", -"pénicillino-résistances", -"pénicillino-sensibilité", -"pénicillino-sensibilités", "Penne-d'Agenais", "Pennes-le-Sec", -"penn-ty", -"pense-bête", -"pense-bêtes", "Penta-Acquatella", -"penta-cœur", -"penta-cœurs", -"penta-continental", -"penta-core", -"penta-cores", "Penta-di-Casinca", -"pen-testeur", -"pen-testeurs", -"pen-testeuse", -"pen-testeuses", -"pen-ty", -"people-isa", -"people-isai", -"people-isaient", -"people-isais", -"people-isait", -"people-isâmes", -"people-isant", -"people-isas", -"people-isasse", -"people-isassent", -"people-isasses", -"people-isassiez", -"people-isassions", -"people-isât", -"people-isâtes", -"people-ise", -"people-isé", -"people-isée", -"people-isées", -"people-isent", -"people-iser", -"people-isera", -"people-iserai", -"people-iseraient", -"people-iserais", -"people-iserait", -"people-iseras", -"people-isèrent", -"people-iserez", -"people-iseriez", -"people-iserions", -"people-iserons", -"people-iseront", -"people-ises", -"people-isés", -"people-isez", -"people-isiez", -"people-isions", -"people-isons", "Percey-le-Grand", "Percey-le-Pautel", "Percey-sous-Montormentier", -"perche-brochet", "Perche-en-Nocé", -"perche-soleil", "Percy-en-Auge", "Percy-en-Normandie", -"perdante-perdante", -"perdantes-perdantes", -"perdant-perdant", -"perdants-perdants", -"perd-sa-queue", -"perd-tout", -"père-la-pudeur", -"Père-la-pudeur", -"pères-la-pudeur", -"Péret-Bel-Air", -"perfo-vérif", "Pergain-Taillac", -"Périers-en-Auge", -"Périers-sur-le-Dan", -"Pérignat-ès-Allier", -"Pérignat-lès-Sarliève", -"Pérignat-sur-Allier", -"Périgny-la-Rose", "Perles-et-Castelet", "Perly-Certoux", "Pernand-Vergelesses", -"Pernes-lès-Boulogne", "Pernes-les-Fontaines", +"Pernes-lès-Boulogne", "Pero-Casevecchie", -"Pérols-sur-Vézère", -"péronéo-calcanéen", -"péronéo-malléolaire", -"péronéo-malléolaires", -"péronéo-phalangien", -"péronéo-tibial", -"Péronne-en-Mélantois", -"Péronnes-lez-Antoing", -"Péroy-les-Gombries", -"Perpète-la-ouf", -"Perpète-les-Alouettes", -"Perpète-les-Oies", -"Perpète-lès-Oies", -"Perpète-les-Olivettes", "Perpette-les-Oies", "Perpezac-le-Blanc", "Perpezac-le-Noir", +"Perpète-la-ouf", +"Perpète-les-Alouettes", +"Perpète-les-Oies", +"Perpète-les-Olivettes", +"Perpète-lès-Oies", "Perrancey-les-Vieux-Moulins", "Perrecy-les-Forges", "Perriers-en-Beauficel", @@ -18567,113 +10321,44 @@ FR_BASE_EXCEPTIONS = [ "Perriers-sur-Andelle", "Perrigny-lès-Dijon", "Perrigny-sur-Armançon", -"Perrigny-sur-l'Ognon", "Perrigny-sur-Loire", +"Perrigny-sur-l'Ognon", "Perrogney-les-Fontaines", -"perroquet-hibou", -"perroquets-hiboux", "Perros-Guirec", -"perruche-moineau", -"perruches-moineaux", -"Pers-en-Gâtinais", "Pers-Jussy", +"Pers-en-Gâtinais", "Perthes-lès-Brienne", "Perthes-lès-Hurlus", "Pertheville-Ners", -"pesco-végétarien", -"pèse-acide", -"pèse-acides", -"pèse-alcool", -"pèse-alcools", -"pèse-bébé", -"pèse-bébés", -"pèse-esprit", -"pèse-esprits", -"pèse-lait", -"pèse-laits", -"pèse-lettre", -"pèse-lettres", -"pèse-liqueur", -"pèse-liqueurs", -"pèse-mout", -"pèse-moût", -"pèse-mouts", -"pèse-moûts", -"pèse-nitre", -"pèse-nitres", -"pèse-personne", -"pèse-personnes", -"pèse-sel", -"pèse-sels", -"pèse-sirop", -"pèse-sirops", -"pèse-vernis", -"Pessac-sur-Dordogne", "Pessa'h", +"Pessac-sur-Dordogne", "Pessat-Villeneuve", -"péta-ampère", -"péta-ampères", -"péta-électron-volt", -"pétaélectron-volt", -"péta-électron-volts", -"pétaélectron-volts", -"pet'che", -"pet-d'âne", -"pet-de-loup", -"pet-de-nonne", -"pet-de-soeur", -"pet-de-sœur", "Petegem-aan-de-Leie", "Petegem-aan-de-Schelde", -"pet-en-l'air", "Peterswald-Löffelscheid", -"pète-sec", -"pète-sèche", -"pète-sèches", -"pète-secs", -"petites-bourgeoises", -"petites-bourgeoisies", -"petites-filles", -"petites-mains", -"petites-maîtresses", -"petites-nièces", -"petites-russes", -"petits-beurre", -"petits-bourgeois", -"petits-chênes", -"petits-déjeuners", -"petits-ducs", -"petits-enfants", -"petits-fils", -"petits-fours", -"petits-gris", -"petits-laits", -"petits-maîtres", -"petits-neveux", -"petits-russes", -"petits-suisses", -"petits-trains", +"Petit-Auverné", +"Petit-Bersac", +"Petit-Bourg", +"Petit-Canal", +"Petit-Caux", +"Petit-Couronne", +"Petit-Croix", +"Petit-Failly", +"Petit-Fayt", +"Petit-Landau", +"Petit-Mars", +"Petit-Mesnil", +"Petit-Noir", +"Petit-Palais-et-Cornemps", +"Petit-Réderching", +"Petit-Tenquin", +"Petit-Verly", +"Petite-Chaux", +"Petite-Forêt", +"Petite-Rosselle", +"Petite-Île", "Petreto-Bicchisano", -"pétrolier-minéralier", -"pétro-monarchie", -"pétro-monarchies", -"pétro-occipital", -"pétro-salpingo-staphylin", -"pétro-salpingo-staphylins", -"pétro-staphylin", -"pétrus-colien", -"Pétrus-Colien", -"pétrus-colienne", -"Pétrus-Colienne", -"pétrus-coliennes", -"Pétrus-Coliennes", -"pétrus-coliens", -"Pétrus-Coliens", -"pets-de-loup", -"pets-de-nonne", -"peul-peul", "Peumerit-Quintin", -"peut-être", "Peux-et-Couffouleux", "Peypin-d'Aigues", "Peyrat-de-Bellac", @@ -18684,778 +10369,348 @@ FR_BASE_EXCEPTIONS = [ "Peyrefitte-sur-l'Hers", "Peyrelongue-Abos", "Peyret-Saint-André", -"Peyriac-de-Mer", "Peyriac-Minervois", +"Peyriac-de-Mer", "Peyrillac-et-Millac", "Peyrolles-en-Provence", "Peyrusse-Grande", -"Peyrusse-le-Roc", "Peyrusse-Massas", "Peyrusse-Vieille", +"Peyrusse-le-Roc", "Peyzac-le-Moustier", "Peyzieux-sur-Saône", "Pezé-le-Robert", -"Pézènes-les-Mines", -"Pézilla-de-Conflent", -"Pézilla-la-Rivière", +"Peñacerrada-Urizaharra", +"Peñarroya-Pueblonuevo", "Pfaffen-Schwabenheim", -"P-frame", -"p-graphe", -"p-graphes", -"pharyngo-laryngite", -"pharyngo-laryngites", -"pharyngo-staphylin", -"phénico-punique", -"phénico-puniques", -"philosopho-théologique", -"philosopho-théologiques", -"pH-mètre", -"phonético-symbolique", -"phoque-garou", -"phoque-léopard", -"phoques-garous", -"phoséthyl-Al", -"phosétyl-Al", -"phosphate-allophane", -"phosphate-allophanes", -"photos-finish", -"phragmito-scirpaie", -"phragmito-scirpaies", -"phrase-clé", -"phrases-clés", -"phréno-glottisme", -"phréno-glottismes", -"physico-chimie", -"physico-chimies", -"physico-chimique", -"physico-chimiques", -"physico-mathématique", -"physico-mathématiques", -"physio-pathologie", -"physio-pathologies", -"piane-piane", -"piano-bar", -"piano-bars", -"piano-forte", -"piano-fortes", -"piano-manivelle", +"Pi-Ramsès", "Pianotolli-Caldarello", "Pianottoli-Caldarello", -"pian's", -"pichot-chêne", -"pichots-chênes", -"pick-up", -"pick-ups", -"pico-condensateur", -"pico-condensateurs", -"pico-ohm", -"pico-ohms", -"pics-verts", "Picto-Charentais", -"pic-vert", -"pic-verts", -"pidgin-english", -"pièces-au-cul", -"pied-à-terre", -"pied-bot", -"pied-d'alouette", -"pied-de-banc", -"pied-de-biche", -"pied-de-boeuf", -"pied-de-bœuf", -"Pied-de-Borne", -"pied-de-chat", -"pied-de-cheval", -"pied-de-chèvre", -"pied-de-coq", -"pied-de-corbeau", -"pied-de-griffon", -"pied-de-lion", -"pied-de-loup", -"pied-de-mouche", -"pied-de-mouton", -"pied-de-pélican", -"pied-de-pigeon", -"pied-de-poule", -"pied-d'étape", -"pied-de-veau", -"pied-d'oiseau", -"pied-droit", -"pié-de-lion", -"pied-fort", -"Piedicorte-di-Gaggio", -"pied-noir", -"pied-noire", -"pied-noirisa", -"pied-noirisai", -"pied-noirisaient", -"pied-noirisais", -"pied-noirisait", -"pied-noirisâmes", -"pied-noirisant", -"pied-noirisas", -"pied-noirisasse", -"pied-noirisassent", -"pied-noirisasses", -"pied-noirisassiez", -"pied-noirisassions", -"pied-noirisât", -"pied-noirisâtes", -"pied-noirise", -"pied-noirisé", -"pied-noirisée", -"pied-noirisées", -"pied-noirisent", -"pied-noiriser", -"pied-noirisera", -"pied-noiriserai", -"pied-noiriseraient", -"pied-noiriserais", -"pied-noiriserait", -"pied-noiriseras", -"pied-noirisèrent", -"pied-noiriserez", -"pied-noiriseriez", -"pied-noiriserions", -"pied-noiriserons", -"pied-noiriseront", -"pied-noirises", -"pied-noirisés", -"pied-noirisez", -"pied-noirisiez", -"pied-noirisions", -"pied-noirisons", "Pie-d'Orezza", -"pied-plat", -"pied-rouge", -"pieds-bots", -"pieds-d'alouette", -"pieds-de-biche", -"pieds-de-boeuf", -"pieds-de-bœuf", -"pieds-de-chat", -"pieds-de-chèvre", -"pieds-de-coq", -"pieds-de-corbeau", -"pieds-de-griffon", -"pieds-de-lion", -"pieds-de-mouche", -"pieds-de-mouton", -"pieds-de-veau", -"pieds-d'oiseau", -"pieds-droits", -"pieds-forts", -"pieds-noires", -"pieds-noirs", -"pieds-paquets", -"pieds-plats", -"pieds-tendres", -"pied-tendre", -"pied-vert", -"piège-à-cons", -"pièges-à-cons", -"pie-grièche", -"Piégros-la-Clastre", -"Piégut-Pluviers", -"pie-mère", +"Pied-de-Borne", +"Piedicorte-di-Gaggio", "Piennes-Onvillers", -"pie-noir", -"pie-noire", -"pie-noires", -"pie-noirs", -"pie-rouge", -"pierre-bénitain", +"Pierre-Buffière", +"Pierre-Buffiérois", +"Pierre-Buffiéroise", +"Pierre-Buffiéroises", "Pierre-Bénitain", -"pierre-bénitaine", "Pierre-Bénitaine", -"pierre-bénitaines", "Pierre-Bénitaines", -"pierre-bénitains", "Pierre-Bénitains", "Pierre-Bénite", -"Pierre-Buffière", -"pierre-buffiérois", -"Pierre-Buffiérois", -"pierre-buffiéroise", -"Pierre-Buffiéroise", -"pierre-buffiéroises", -"Pierre-Buffiéroises", "Pierre-Chanel", "Pierre-Châtel", -"pierre-châtelois", "Pierre-Châtelois", -"pierre-châteloise", "Pierre-Châteloise", -"pierre-châteloises", "Pierre-Châteloises", +"Pierre-Levée", +"Pierre-Levéen", +"Pierre-Levéenne", +"Pierre-Levéennes", +"Pierre-Levéens", +"Pierre-Louis", +"Pierre-Marie", +"Pierre-Montois", +"Pierre-Montoise", +"Pierre-Montoises", +"Pierre-Morains", +"Pierre-Olivier", +"Pierre-Percée", +"Pierre-Perthuis", +"Pierre-Yves", "Pierre-de-Bresse", +"Pierre-la-Treiche", "Pierrefeu-du-Var", -"pierre-feuille-ciseaux", +"Pierrefitte-Nestalas", "Pierrefitte-en-Auge", "Pierrefitte-en-Beauvaisis", "Pierrefitte-en-Cinglais", -"Pierrefitte-ès-Bois", -"Pierrefitte-Nestalas", "Pierrefitte-sur-Aire", "Pierrefitte-sur-Loire", "Pierrefitte-sur-Sauldre", "Pierrefitte-sur-Seine", -"Pierrefontaine-lès-Blamont", +"Pierrefitte-ès-Bois", "Pierrefontaine-les-Varans", -"Pierre-la-Treiche", -"Pierre-Levée", -"pierre-levéen", -"Pierre-Levéen", -"pierre-levéenne", -"Pierre-Levéenne", -"pierre-levéennes", -"Pierre-Levéennes", -"pierre-levéens", -"Pierre-Levéens", -"Pierre-Louis", -"Pierre-Marie", -"pierre-montois", -"Pierre-Montois", -"pierre-montoise", -"Pierre-Montoise", -"pierre-montoises", -"Pierre-Montoises", +"Pierrefontaine-lès-Blamont", "Pierremont-sur-Amance", -"Pierre-Morains", -"Pierre-Olivier", -"pierre-papier-ciseaux", -"Pierre-Percée", -"Pierre-Perthuis", "Pierrepont-sur-Avre", "Pierrepont-sur-l'Arentèle", -"pierre-qui-vire", -"pierres-qui-virent", -"Pierre-Yves", -"piés-de-lion", -"pies-grièches", -"pies-mères", -"piétin-échaudage", -"piétin-verse", "Pietra-di-Verde", "Piets-Plasence-Moustrou", -"piézo-électricité", -"piézo-électricités", -"piézo-électrique", -"piézo-électriques", "Pihen-lès-Guînes", "Pijnacker-Nootdorp", "Pila-Canale", -"pile-poil", -"pilo-sébacé", "Pin-Balma", -"pince-balle", -"pince-balles", -"pince-érigne", -"pince-érignes", -"pince-fesse", -"pince-fesses", -"pince-lisière", -"pince-maille", -"pince-mailles", -"pince-monseigneur", -"pince-nez", -"pince-notes", -"pince-oreille", -"pince-oreilles", -"pince-sans-rire", -"pinces-monseigneur", -"Pinel-Hauterive", -"ping-pong", -"ping-pongs", "Pin-Moriès", -"pino-balméen", -"Pino-Balméen", -"pino-balméenne", -"Pino-Balméenne", -"pino-balméennes", -"Pino-Balméennes", -"pino-balméens", -"Pino-Balméens", -"pin-pon", -"pin's", "Pin-Saint-Denis", +"Pinel-Hauterive", +"Pino-Balméen", +"Pino-Balméenne", +"Pino-Balméennes", +"Pino-Balméens", "Pins-Justaret", -"pins-justarétois", "Pins-Justarétois", -"pins-justarétoise", "Pins-Justarétoise", -"pins-justarétoises", "Pins-Justarétoises", -"Piñuécar-Gandullas", -"pin-up", -"piou-piou", -"piou-pious", -"pipe-line", -"pipe-lines", -"piqueur-suceur", -"Pi-Ramsès", -"Piré-sur-Seiche", "Piriac-sur-Mer", -"pirimiphos-éthyl", -"pirimiphos-méthyl", -"pis-aller", -"pis-allers", -"pisse-au-lit", -"pisse-chien", -"pisse-chiens", -"pisse-copie", -"pisse-copies", -"pisse-debout", -"pisse-froid", -"pisse-mémé", -"pisse-mémère", -"pisse-sang", -"pisse-trois-gouttes", -"pisse-vinaigre", -"pisse-vinaigres", -"pisse-z-yeux", -"pissy-pôvillais", +"Piré-sur-Seiche", "Pissy-Pôvillais", -"pissy-pôvillaise", "Pissy-Pôvillaise", -"pissy-pôvillaises", "Pissy-Pôvillaises", "Pissy-Pôville", -"pistillo-staminé", -"pistolet-mitrailleur", -"pistolets-mitrailleurs", -"pit-bulls", "Pithiviers-le-Vieil", -"pixie-bob", +"Piégros-la-Clastre", +"Piégut-Pluviers", +"Piñuécar-Gandullas", "Plachy-Buyon", -"plachy-buyonnais", "Plachy-Buyonnais", -"plachy-buyonnaise", "Plachy-Buyonnaise", -"plachy-buyonnaises", "Plachy-Buyonnaises", "Placy-Montaigu", -"Plaimbois-du-Miroir", "Plaimbois-Vennes", +"Plaimbois-du-Miroir", "Plaimpied-Givaudins", -"plain-chant", "Plain-de-Corravillers", -"Plaine-de-Walsch", "Plaine-Haute", +"Plaine-de-Walsch", "Plaines-Saint-Lange", -"plain-pied", -"plains-chants", -"plains-pieds", "Plaisance-du-Touch", -"plaît-il", -"Plancher-Bas", -"Plancher-les-Mines", -"planches-contacts", -"Plancy-l'Abbaye", "Plan-d'Aups", "Plan-d'Aups-Sainte-Baume", +"Plan-d'Orgon", "Plan-de-Baix", "Plan-de-Cuques", "Plan-de-la-Tour", -"Plan-d'Orgon", "Plan-les-Ouates", -"plan-masse", -"plan-plan", -"plan-planisme", -"plan-planismes", -"plan-séquence", -"plan-séquences", -"plans-masses", -"plan-socialisa", -"plan-socialisai", -"plan-socialisaient", -"plan-socialisais", -"plan-socialisait", -"plan-socialisâmes", -"plan-socialisant", -"plan-socialisas", -"plan-socialisasse", -"plan-socialisassent", -"plan-socialisasses", -"plan-socialisassiez", -"plan-socialisassions", -"plan-socialisât", -"plan-socialisâtes", -"plan-socialise", -"plan-socialisé", -"plan-socialisée", -"plan-socialisées", -"plan-socialisent", -"plan-socialiser", -"plan-socialisera", -"plan-socialiserai", -"plan-socialiseraient", -"plan-socialiserais", -"plan-socialiserait", -"plan-socialiseras", -"plan-socialisèrent", -"plan-socialiserez", -"plan-socialiseriez", -"plan-socialiserions", -"plan-socialiserons", -"plan-socialiseront", -"plan-socialises", -"plan-socialisés", -"plan-socialisez", -"plan-socialisiez", -"plan-socialisions", -"plan-socialisons", -"plans-séquences", -"plante-crayon", -"plante-éponge", -"plantes-crayons", -"plaque-bière", -"plaque-tonnerre", +"Plancher-Bas", +"Plancher-les-Mines", +"Plancy-l'Abbaye", "Plassac-Rouffiac", -"plat-bord", -"plat-cul", -"plat-culs", -"plat-de-bierre", "Plateau-Central", -"plateau-repas", -"plateaux-repas", -"plate-bande", -"plate-bière", -"plate-face", -"plate-forme", -"plate-longe", -"plates-bandes", -"plates-formes", -"plates-longes", -"platinico-ammonique", -"plats-bords", -"play-back", -"play-backs", -"play-boy", -"play-boys", -"play-off", -"play-offs", -"plein-cintre", -"pleine-fougerais", "Pleine-Fougerais", -"pleine-fougeraise", "Pleine-Fougeraise", -"pleine-fougeraises", "Pleine-Fougeraises", "Pleine-Fougères", -"plein-emploi", "Pleine-Selve", "Pleine-Sève", "Pleines-Œuvres", -"pleins-cintres", "Pleisweiler-Oberhofen", -"Plélan-le-Grand", -"Plélan-le-Petit", -"Plénée-Jugon", -"Pléneuf-Val-André", "Pleslin-Trigavou", -"plessis-ansoldien", "Plessis-Ansoldien", -"plessis-ansoldienne", "Plessis-Ansoldienne", -"plessis-ansoldiennes", "Plessis-Ansoldiennes", -"plessis-ansoldiens", "Plessis-Ansoldiens", "Plessis-Barbuise", -"plessis-brionnais", "Plessis-Brionnais", -"plessis-brionnaise", "Plessis-Brionnaise", -"plessis-brionnaises", "Plessis-Brionnaises", -"plessis-bucardésien", "Plessis-Bucardésien", -"plessis-bucardésienne", "Plessis-Bucardésienne", -"plessis-bucardésiennes", "Plessis-Bucardésiennes", -"plessis-bucardésiens", "Plessis-Bucardésiens", -"Plessis-de-Roye", -"Plessis-du-Mée", -"plessis-episcopien", "Plessis-Episcopien", -"plessis-épiscopien", -"Plessis-Épiscopien", -"plessis-episcopienne", "Plessis-Episcopienne", -"plessis-épiscopienne", -"Plessis-Épiscopienne", -"plessis-episcopiennes", "Plessis-Episcopiennes", -"plessis-épiscopiennes", -"Plessis-Épiscopiennes", -"plessis-episcopiens", "Plessis-Episcopiens", -"plessis-épiscopiens", -"Plessis-Épiscopiens", "Plessis-Gatebled", -"plessis-grammoirien", "Plessis-Grammoirien", -"plessis-grammoirienne", "Plessis-Grammoirienne", -"plessis-grammoiriennes", "Plessis-Grammoiriennes", -"plessis-grammoiriens", "Plessis-Grammoiriens", -"plessis-luzarchois", "Plessis-Luzarchois", -"plessis-luzarchoise", "Plessis-Luzarchoise", -"plessis-luzarchoises", "Plessis-Luzarchoises", -"plessis-macéen", "Plessis-Macéen", -"plessis-macéenne", "Plessis-Macéenne", -"plessis-macéennes", "Plessis-Macéennes", -"plessis-macéens", "Plessis-Macéens", "Plessis-Saint-Benoist", "Plessis-Saint-Jean", +"Plessis-de-Roye", +"Plessis-du-Mée", +"Plessis-Épiscopien", +"Plessis-Épiscopienne", +"Plessis-Épiscopiennes", +"Plessis-Épiscopiens", "Plessix-Balisson", "Plestin-les-Grèves", "Pleudihen-sur-Rance", "Pleumeur-Bodou", "Pleumeur-Gautier", -"pleu-pleu", -"pleure-misère", -"pleure-misères", -"pleuronecte-guitare", -"pleuro-péricardite", "Pleyber-Christ", -"plieuse-inséreuse", -"plieuses-inséreuses", "Plobannalec-Lesconil", -"Plœuc-L'Hermitage", -"Plœuc-sur-Lié", +"Ploeuc-L'Hermitage", "Plogastel-Saint-Germain", "Plombières-les-Bains", "Plombières-lès-Dijon", "Plonéour-Lanvern", -"Plonévez-du-Faou", "Plonévez-Porzay", -"plongée-spéléo", -"plongées-spéléo", +"Plonévez-du-Faou", "Plorec-sur-Arguenon", -"Plouëc-du-Trieux", -"Plouégat-Guérand", -"Plouégat-Moysan", -"Plouër-sur-Rance", "Plouezoc'h", -"plouezoc'hois", "Plouezoc'hois", -"plouezoc'hoise", "Plouezoc'hoise", -"plouezoc'hoises", "Plouezoc'hoises", "Plougastel-Daoulas", "Ploulec'h", -"ploulec'hois", "Ploulec'hois", -"ploulec'hoise", "Ploulec'hoise", -"ploulec'hoises", "Ploulec'hoises", "Ploumanac'h", "Plounéour-Brignogan-Plages", "Plounéour-Ménez", "Plounéour-Trez", -"plounéour-trezien", "Plounéour-Trezien", -"plounéour-trezienne", "Plounéour-Trezienne", -"plounéour-treziennes", "Plounéour-Treziennes", -"plounéour-treziens", "Plounéour-Treziens", "Plounévez-Lochrist", "Plounévez-Moëdec", "Plounévez-Quintin", "Plourac'h", "Plourin-lès-Morlaix", +"Plouégat-Guérand", +"Plouégat-Moysan", +"Plouëc-du-Trieux", +"Plouër-sur-Rance", "Ployart-et-Vaurseine", -"ploye-ressort", -"plui-plui", -"plumbo-aragonite", -"plumbo-aragonites", -"plum-cake", -"plum-cakes", -"plume-couteau", -"plumes-couteaux", -"plum-pudding", -"plû-part", -"pluri-continental", -"pluri-interprétable", -"pluri-interprétables", -"pluri-journalier", -"pluri-modal", -"pluri-national", -"pluri-nationale", -"pluri-nationales", -"pluri-nationaux", -"plus-d'atouts", -"plus-disant", -"plus-part", -"plus-payé", -"plus-pétition", -"plus-produit", -"plus-produits", -"plus-que-parfait", -"plus-que-parfaits", -"plus-value", -"plus-values", -"pluto-neptunien", -"pluvier-hirondelle", +"Plélan-le-Grand", +"Plélan-le-Petit", +"Pléneuf-Val-André", +"Plénée-Jugon", +"Plœuc-L'Hermitage", +"Plœuc-sur-Lié", "Pobé-Mengao", "Pocé-les-Bois", "Pocé-sur-Cisse", -"poche-cuiller", -"poche-revolver", -"poches-revolver", -"pochettes-surprise", -"pochettes-surprises", -"pochette-surprise", -"podio-régalien", "Podio-Régalien", -"podio-régalienne", "Podio-Régalienne", -"podio-régaliennes", "Podio-Régaliennes", -"podio-régaliens", "Podio-Régaliens", -"podo-orthésiste", -"podo-orthésistes", -"poët-lavalien", -"Poët-Lavalien", -"poët-lavalienne", -"Poët-Lavalienne", -"poët-lavaliennes", -"Poët-Lavaliennes", -"poët-lavaliens", -"Poët-Lavaliens", -"Poey-de-Lescar", "Poey-d'Oloron", +"Poey-de-Lescar", +"Poggio-Marinaccio", +"Poggio-Mezzana", +"Poggio-Mezzanais", +"Poggio-Mezzanaise", +"Poggio-Mezzanaises", +"Poggio-d'Oletta", "Poggio-di-Nazza", "Poggio-di-Tallano", "Poggio-di-Venaco", -"Poggio-d'Oletta", -"Poggio-Marinaccio", -"Poggio-Mezzana", -"poggio-mezzanais", -"Poggio-Mezzanais", -"poggio-mezzanaise", -"Poggio-Mezzanaise", -"poggio-mezzanaises", -"Poggio-Mezzanaises", -"pogne-cul", -"pogne-culs", "Poids-de-Fiole", -"poids-lourd", -"poids-lourds", "Poigny-la-Forêt", "Poilcourt-Sydney", -"Poillé-sur-Vègre", "Poilly-lez-Gien", "Poilly-sur-Serein", "Poilly-sur-Tholon", -"Poinçon-lès-Larrey", +"Poillé-sur-Vègre", "Poinson-lès-Fayl", "Poinson-lès-Grancey", "Poinson-lès-Nogent", -"point-arrière", -"point-col", -"Pointe-à-Pitre", "Pointe-Claire", -"pointe-de-coeur", -"pointe-de-cœur", -"pointe-de-diamant", -"Pointe-du-Laquois", "Pointe-Fortunais", "Pointe-Fortunien", -"pointe-noirais", "Pointe-Noirais", -"pointe-noiraise", "Pointe-Noiraise", -"pointe-noiraises", "Pointe-Noiraises", "Pointe-Noire", -"pointer-et-cliquer", -"pointes-de-coeur", -"pointes-de-cœur", -"pointes-de-diamant", -"Pointis-de-Rivière", +"Pointe-du-Laquois", +"Pointe-à-Pitre", "Pointis-Inard", -"point-milieu", -"point-selle", -"points-virgules", -"points-voyelles", -"point-virgule", -"point-voyelle", +"Pointis-de-Rivière", +"Poinçon-lès-Larrey", "Poiseul-la-Grange", "Poiseul-la-Ville-et-Laperrière", "Poiseul-lès-Saulx", -"poissonnier-écailler", -"poitevin-saintongeais", "Poitou-Charentes", -"poivre-sel", +"Poix-Terron", "Poix-de-Picardie", "Poix-du-Nord", -"poix-résine", -"Poix-Terron", -"poka-yoké", "Polaincourt-et-Clairefontaine", "Poleymieux-au-Mont-d'Or", "Poliez-Pittet", -"politico-économique", -"politico-économiques", -"politico-idéologique", -"politico-idéologiques", -"politico-médiatique", -"politico-religieuse", -"politico-religieuses", -"politico-religieux", -"pollueur-payeur", -"pollueurs-payeurs", -"poly-articulaire", -"poly-articulaires", -"polychlorodibenzo-p-dioxine", -"polychlorodibenzo-p-dioxines", -"poly-insaturé", -"poly-insaturée", -"poly-insaturées", -"poly-insaturés", -"poly-sexuel", -"poly-sexuelle", -"Poméranie-Occidentale-de-l'Est", -"Poméranie-Occidentale-du-Nord", -"pomme-de-pin", -"pomme-grenade", "Pommerit-Jaudy", "Pommerit-le-Vicomte", -"pommes-de-pin", "Pommier-de-Beaurepaire", -"Pommiers-la-Placette", "Pommiers-Moulons", -"pompages-turbinages", -"pompage-turbinage", +"Pommiers-la-Placette", "Pompierre-sur-Doubs", -"Poncé-sur-le-Loir", +"Poméranie-Occidentale-de-l'Est", +"Poméranie-Occidentale-du-Nord", "Poncey-lès-Athée", "Poncey-sur-l'Ignon", "Ponches-Estruval", +"Poncé-sur-le-Loir", "Ponet-et-Saint-Auban", "Ponlat-Taillebourg", "Ponsan-Soubiran", "Ponson-Debat-Pouts", "Ponson-Dessus", +"Pont de Montvert - Sud Mont Lozère", +"Pont-Arcy", +"Pont-Audemer", +"Pont-Authou", +"Pont-Aven", +"Pont-Bellanger", +"Pont-Croix", +"Pont-Farcy", +"Pont-Hébert", +"Pont-Melvez", +"Pont-Noyelles", +"Pont-Péan", +"Pont-Remy", +"Pont-Saint-Esprit", +"Pont-Saint-Mard", +"Pont-Saint-Martin", +"Pont-Saint-Pierre", +"Pont-Saint-Vincent", +"Pont-Sainte-Marie", +"Pont-Sainte-Maxence", +"Pont-Salomon", +"Pont-Scorff", +"Pont-d'Ain", +"Pont-d'Héry", +"Pont-d'Ouilly", +"Pont-de-Barret", +"Pont-de-Buis-lès-Quimerch", +"Pont-de-Chéruy", +"Pont-de-Labeaume", +"Pont-de-Larn", +"Pont-de-Metz", +"Pont-de-Poitte", +"Pont-de-Roide-Vermondans", +"Pont-de-Ruan", +"Pont-de-Salars", +"Pont-de-Vaux", +"Pont-de-Veyle", +"Pont-de-l'Arche", +"Pont-de-l'Isère", +"Pont-du-Bois", +"Pont-du-Casse", +"Pont-du-Château", +"Pont-du-Navoy", +"Pont-en-Royans", +"Pont-et-Massène", +"Pont-l'Abbé", +"Pont-l'Abbé-d'Arnoult", +"Pont-l'Évêque", +"Pont-la-Ville", +"Pont-les-Moulins", +"Pont-lès-Bonfays", +"Pont-sur-Madon", +"Pont-sur-Meuse", +"Pont-sur-Sambre", +"Pont-sur-Seine", +"Pont-sur-Vanne", +"Pont-sur-Yonne", +"Pont-sur-l'Ognon", +"Pont-Évêque", +"Pont-à-Marcq", +"Pont-à-Mousson", +"Pont-à-Vendin", "Pontailler-sur-Saône", "Pontamafrey-Montpascal", "Pontault-Combault", @@ -19467,97 +10722,69 @@ FR_BASE_EXCEPTIONS = [ "Pontiacq-Viellepinte", "Pontoise-lès-Noyon", "Pontonx-sur-l'Adour", -"ponts-bascules", -"ponts-canaux", -"ponts-de-céais", "Ponts-de-Céais", -"ponts-de-céaise", "Ponts-de-Céaise", -"ponts-de-céaises", "Ponts-de-Céaises", "Ponts-et-Marais", -"ponts-levis", -"ponts-neufs", -"popa'a", -"pop-corn", -"pop-in", -"pop-ins", -"pop-punk", -"pop-up", -"pop-ups", -"porc-épic", "Porcieu-Amblagnieu", -"porcs-épics", +"Port-Brillet", +"Port-Jérôme-sur-Seine", +"Port-Launay", +"Port-Lesney", +"Port-Louis", +"Port-Mort", +"Port-Saint-Louis-du-Rhône", +"Port-Saint-Père", +"Port-Sainte-Foy-et-Ponchapt", +"Port-Sainte-Marie", +"Port-Vendres", +"Port-Villez", +"Port-d'Envaux", +"Port-de-Bouc", +"Port-de-Lanne", +"Port-de-Piles", +"Port-des-Barques", +"Port-en-Bessin-Huppain", +"Port-la-Nouvelle", +"Port-le-Grand", +"Port-sur-Saône", +"Port-sur-Seille", +"Porte-Joie", "Portel-des-Corbières", -"Porté-Puymorens", "Portes-en-Valdaine", -"portes-fenêtres", "Portes-lès-Valence", -"portes-tambour", "Portet-d'Aspet", "Portet-de-Luchon", "Portet-sur-Garonne", -"porteur-de-peau", "Porto-Novo", "Porto-Ricain", "Porto-Ricaine", "Porto-Ricaines", "Porto-Ricains", "Porto-Rico", -"porto-vecchiais", "Porto-Vecchiais", -"porto-vecchiaise", "Porto-Vecchiaise", -"porto-vecchiaises", "Porto-Vecchiaises", "Porto-Vecchio", -"portrait-charge", -"portrait-robot", -"portraits-charges", -"portraits-robots", -"posé-décollé", -"posé-décollés", -"pose-tubes", -"post-11-Septembre", +"Porté-Puymorens", "Postbauer-Heng", -"potassico-ammonique", -"potassico-mercureux", -"pot-au-feu", -"pot-au-noir", -"pot-beurrier", -"pot-bouille", -"pot-de-vin", -"pot-en-tête", -"poto-poto", -"pot-pourri", -"potron-jacquet", -"potron-minet", "Potsdam-Mittelmark", -"pots-de-vin", -"pots-pourris", "Pouan-les-Vallées", -"pouce-pied", -"pouces-pieds", -"pou-de-soie", -"poudre-éclair", -"poudres-éclair", -"poudres-éclairs", -"pouët-pouët", "Pougne-Hérisson", "Pougues-les-Eaux", -"Pouillé-les-Côteaux", "Pouilley-Français", "Pouilley-les-Vignes", "Pouilly-en-Auxois", "Pouilly-le-Monial", -"Pouilly-lès-Feurs", "Pouilly-les-Nonains", +"Pouilly-lès-Feurs", "Pouilly-sous-Charlieu", "Pouilly-sur-Loire", "Pouilly-sur-Meuse", "Pouilly-sur-Saône", "Pouilly-sur-Serre", "Pouilly-sur-Vingeanne", +"Pouillé-les-Côteaux", "Poulan-Pouzols", "Pouldavid-sur-Mer", "Poule-les-Echarmeaux", @@ -19565,76 +10792,40 @@ FR_BASE_EXCEPTIONS = [ "Pouligney-Lusans", "Pouligny-Notre-Dame", "Pouligny-Saint-Martin", -"pouligny-saint-pierre", "Pouligny-Saint-Pierre", "Poullan-sur-Mer", -"poult-de-soie", "Poulton-le-Fylde", -"poults-de-soie", "Pouques-Lormes", -"pour-boire", -"pour-cent", "Pournoy-la-Chétive", "Pournoy-la-Grasse", -"pourri-gâté", "Poursay-Garnaud", "Poursiugues-Boucoue", -"poursuite-bâillon", -"Pouru-aux-Bois", "Pouru-Saint-Remy", -"pousse-au-crime", -"pousse-au-jouir", -"pousse-au-vice", -"pousse-broche", -"pousse-broches", -"pousse-café", -"pousse-cafés", -"pousse-caillou", -"pousse-cailloux", -"pousse-cambrure", -"pousse-cambrures", -"pousse-cul", -"pousse-culs", -"pousse-fiche", -"pousse-goupille", -"pousse-mégot", -"pousse-mégots", -"pousse-navette", -"pousse-pied", -"pousse-pieds", -"pousse-pointe", -"pousse-pointes", -"pousse-pousse", +"Pouru-aux-Bois", "Poussy-la-Campagne", -"pout-de-soie", -"pouts-de-soie", -"poux-de-soie", -"Pouy-de-Touges", "Pouy-Loubrin", -"pouy-roquelain", "Pouy-Roquelain", -"pouy-roquelaine", "Pouy-Roquelaine", -"pouy-roquelaines", "Pouy-Roquelaines", -"pouy-roquelains", "Pouy-Roquelains", "Pouy-Roquelaure", +"Pouy-de-Touges", "Pouy-sur-Vannes", "Pouzols-Minervois", "Pouzy-Mésangy", -"pow-wow", -"pow-wows", "Pozo-Lorente", -"PPD-T", +"Poët-Lavalien", +"Poët-Lavalienne", +"Poët-Lavaliennes", +"Poët-Lavaliens", "Pradelles-Cabardès", "Pradelles-en-Val", -"Pradère-les-Bourguets", +"Prades-Salars", "Prades-d'Aubrac", "Prades-le-Lez", -"Prades-Salars", "Prades-sur-Vernazobre", "Prads-Haute-Bléone", +"Pradère-les-Bourguets", "Pralognan-la-Vanoise", "Prat-Bonrepaux", "Prat-et-Bonrepaux", @@ -19645,212 +10836,49 @@ FR_BASE_EXCEPTIONS = [ "Prats-de-Sournia", "Prats-du-Périgord", "Praz-sur-Arly", -"Préaux-Bocage", -"Préaux-du-Perche", -"Préaux-Saint-Sébastien", -"Préchacq-Josbaig", -"Préchacq-les-Bains", -"Préchacq-Navarrenx", -"Préchac-sur-Adour", -"Précy-le-Sec", -"Précy-Notre-Dame", -"Précy-Saint-Martin", -"Précy-sous-Thil", -"Précy-sur-Marne", -"Précy-sur-Oise", -"Précy-sur-Vrin", "Pregny-Chambésy", "Premeaux-Prissey", -"premier-ministra", "Premier-ministrable", "Premier-ministrables", -"premier-ministrai", -"premier-ministraient", -"premier-ministrais", -"premier-ministrait", -"premier-ministrâmes", -"premier-ministrant", -"premier-ministras", -"premier-ministrasse", -"premier-ministrassent", -"premier-ministrasses", -"premier-ministrassiez", -"premier-ministrassions", -"premier-ministrât", -"premier-ministrâtes", -"premier-ministre", -"premier-ministré", -"premier-ministrée", -"premier-ministrées", -"premier-ministrent", -"premier-ministrer", -"premier-ministrera", -"premier-ministrerai", -"premier-ministreraient", -"premier-ministrerais", -"premier-ministrerait", -"premier-ministreras", -"premier-ministrèrent", -"premier-ministrerez", -"premier-ministreriez", -"premier-ministrerions", -"premier-ministrerons", -"premier-ministreront", -"premier-ministres", -"premier-ministrés", -"premier-ministrez", -"premier-ministriez", -"premier-ministrions", -"premier-ministrons", -"premier-né", -"premiers-nés", "Premosello-Chiovenda", -"prés-bois", -"président-candidat", -"présidente-candidate", -"présidentes-candidates", -"présidents-candidats", -"présidents-directeurs", "Presles-en-Brie", "Presles-et-Boves", "Presles-et-Thierny", -"presqu'accident", -"presqu'accidents", -"presqu'ile", -"presqu'île", -"presqu'iles", -"presqu'îles", "Pressagny-l'Orgueilleux", -"prés-salés", -"press-book", -"press-books", -"presse-agrume", -"presse-agrumes", -"presse-ail", -"presse-artère", -"presse-artères", -"presse-citron", -"presse-citrons", -"presse-étoffe", -"presse-étoffes", -"presse-étoupe", -"presse-étoupes", -"presse-fruits", -"presse-légumes", -"presse-papier", -"presse-papiers", -"presse-purée", -"presse-purées", -"presse-urètre", -"presse-urètres", -"pressignaco-vicois", -"Pressignaco-Vicois", -"pressignaco-vicoise", -"Pressignaco-Vicoise", -"pressignaco-vicoises", -"Pressignaco-Vicoises", "Pressignac-Vicq", +"Pressignaco-Vicois", +"Pressignaco-Vicoise", +"Pressignaco-Vicoises", "Pressigny-les-Pins", "Pressy-sous-Dondin", -"prés-vergers", -"prêt-à-monter", -"prêt-à-penser", -"prêt-à-porter", -"prêt-à-poster", -"prête-nom", -"prête-noms", -"Prétot-Sainte-Suzanne", -"Prétot-Vicquemare", -"prêtres-ouvriers", -"prêts-à-penser", -"prêts-à-porter", "Pretz-en-Argonne", "Preuilly-la-Ville", "Preuilly-sur-Claise", "Preutin-Higny", +"Preux-Romanien", +"Preux-Romanienne", +"Preux-Romaniennes", +"Preux-Romaniens", "Preux-au-Bois", "Preux-au-Sart", -"preux-romanien", -"Preux-Romanien", -"preux-romanienne", -"Preux-Romanienne", -"preux-romaniennes", -"Preux-Romaniennes", -"preux-romaniens", -"Preux-Romaniens", -"Prévessin-Moëns", "Preyssac-d'Excideuil", "Prez-sous-Lafauche", "Prez-sur-Marne", "Prez-vers-Noréaz", -"prie-Dieu", "Prignac-en-Médoc", "Prignac-et-Marcamps", "Prignitz-de-l'Est-Ruppin", -"prima-mensis", -"prime-sautier", -"prim'holstein", -"prince-édouardien", -"Prince-Édouardien", -"prince-édouardienne", -"Prince-Édouardienne", -"prince-édouardiennes", -"Prince-Édouardiennes", -"prince-édouardiens", -"Prince-Édouardiens", -"prince-électeur", -"prince-président", -"prince-sans-rire", -"princes-électeurs", -"princes-présidents", -"Principauté-Ultérieure", "Prin-Deyrançon", +"Prince-Édouardien", +"Prince-Édouardienne", +"Prince-Édouardiennes", +"Prince-Édouardiens", +"Principauté-Ultérieure", "Prinsuéjols-Malbouzon", -"prisons-écoles", "Prissé-la-Charrière", -"privat-docent", -"privat-docentisme", -"privat-docentismes", -"prix-choc", -"prix-chocs", "Prix-lès-Mézières", -"p'rlotte", "Proche-Orient", "Proença-a-Nova", -"programme-cadre", -"programmes-cadres", -"prohexadione-calcium", -"promène-couillon", -"promène-couillons", -"promis-juré", -"promis-jurée", -"promis-jurées", -"promis-jurés", -"prône-misère", -"pronom-adjectif", -"pronoms-adjectifs", -"propre-à-rien", -"propres-à-rien", -"prostato-péritonéal", -"prostato-péritonéale", -"prostato-péritonéales", -"prostato-péritonéaux", -"protège-cahier", -"protège-cahiers", -"protège-dent", -"protège-dents", -"protège-mamelon", -"protège-mamelons", -"protège-oreille", -"protège-oreilles", -"protège-slip", -"protège-slips", -"protège-tibia", -"protège-tibias", -"prout-prout", -"prout-proute", -"prout-proutes", -"prout-prouts", "Provence-Alpes-Côte-d'Azur", "Provenchères-et-Colroy", "Provenchères-lès-Darney", @@ -19859,15 +10887,9 @@ FR_BASE_EXCEPTIONS = [ "Provenchères-sur-Meuse", "Provinces-Unies", "Proviseux-et-Plesnoy", -"prud'homal", -"prud'homale", -"prud'homales", -"prud'homaux", -"prud'homie", -"prud'homies", -"Pruillé-le-Chétif", "Pruillé-l'Eguillé", "Pruillé-l'Éguillé", +"Pruillé-le-Chétif", "Prunay-Belleville", "Prunay-Cassereau", "Prunay-en-Yvelines", @@ -19876,492 +10898,175 @@ FR_BASE_EXCEPTIONS = [ "Prunay-sur-Essonne", "Prunelli-di-Casacconi", "Prunelli-di-Fiumorbo", -"Prunet-et-Belpuig", -"prunet-puigois", "Prunet-Puigois", -"prunet-puigoise", "Prunet-Puigoise", -"prunet-puigoises", "Prunet-Puigoises", -"prunier-cerise", -"pruniers-cerises", +"Prunet-et-Belpuig", "Pruniers-en-Sologne", "Prusly-sur-Ource", "Prusse-Orientale", -"pschitt-pschitt", -"psycho-physiologique", -"psycho-physiologiques", -"psycho-physique", -"psycho-physiques", -"psycho-pop", -"p'tain", -"ptérygo-pharyngien", -"p't-être", -"p'tit", -"p'tite", -"p'tites", -"p'tits", -"pub-restaurant", -"pub-restaurants", -"puce-chique", -"puces-chiques", +"Pré-Saint-Martin", +"Pré-Saint-Évroult", +"Pré-en-Pail-Saint-Samson", +"Préaux-Bocage", +"Préaux-Saint-Sébastien", +"Préaux-du-Perche", +"Préchac-sur-Adour", +"Préchacq-Josbaig", +"Préchacq-Navarrenx", +"Préchacq-les-Bains", +"Précy-Notre-Dame", +"Précy-Saint-Martin", +"Précy-le-Sec", +"Précy-sous-Thil", +"Précy-sur-Marne", +"Précy-sur-Oise", +"Précy-sur-Vrin", +"Prétot-Sainte-Suzanne", +"Prétot-Vicquemare", +"Prévessin-Moëns", "Puch-d'Agenais", "Puech-Cabrier", -"pue-la-sueur", "Puget-Rostang", -"Puget-sur-Argens", "Puget-Théniers", "Puget-Ville", +"Puget-sur-Argens", "Pugny-Chatenod", "Puig-reig", "Puilly-et-Charbeaux", "Puiselet-le-Marais", -"puiset-doréen", "Puiset-Doréen", -"puiset-doréenne", "Puiset-Doréenne", -"puiset-doréennes", "Puiset-Doréennes", -"puiset-doréens", "Puiset-Doréens", +"Puiseux-Pontoise", "Puiseux-en-Bray", "Puiseux-en-France", "Puiseux-en-Retz", "Puiseux-le-Hauberger", "Puiseux-les-Louvres", -"Puiseux-Pontoise", "Puisieux-et-Clanlieu", -"puis-je", "Puits-et-Nuisement", "Puits-la-Lande", "Puits-la-Vallée", "Pujo-le-Plan", "Pujols-sur-Ciron", "Puligny-Montrachet", -"pull-buoy", -"pull-buoys", -"pull-over", -"pull-overs", -"pull-up", -"pulmo-aortique", -"pulso-réacteurs", -"pulvérisateur-mélangeur", -"punaise-mouche", -"punaises-mouches", -"punching-ball", -"punching-balls", -"punkah-wallah", -"pure-laine", -"purge-mariage", -"purge-mariages", -"pur-sang", -"pur-sangs", -"purs-sangs", -"push-back", -"push-up", "Pusy-et-Epenoux", "Pusy-et-Épenoux", -"Putanges-le-Lac", "Putanges-Pont-Ecrepin", "Putanges-Pont-Écrepin", -"putot-bessinois", +"Putanges-le-Lac", "Putot-Bessinois", -"putot-bessinoise", "Putot-Bessinoise", -"putot-bessinoises", "Putot-Bessinoises", "Putot-en-Auge", "Putot-en-Bessin", "Puttelange-aux-Lacs", "Puttelange-lès-Farschviller", "Puttelange-lès-Thionville", +"Puy-Guillaume", +"Puy-Malsignat", +"Puy-Saint-André", +"Puy-Saint-Eusèbe", +"Puy-Saint-Gulmier", +"Puy-Saint-Martin", +"Puy-Saint-Pierre", +"Puy-Saint-Vincent", +"Puy-Sanières", +"Puy-d'Arnac", +"Puy-de-Serre", +"Puy-du-Lac", +"Puy-l'Évêque", "Puygaillard-de-Lomagne", "Puygaillard-de-Quercy", "Puyol-Cazalet", -"pyraflufen-éthyl", "Pyrénées-Atlantiques", "Pyrénées-Orientales", -"pyrimiphos-éthyl", -"pyrimiphos-méthyl", -"pyro-électricité", -"pyro-électricités", -"pyro-électrique", -"pyro-électriques", -"q'anjob'al", +"Pätow-Steegen", +"Père-la-pudeur", +"Pécharic-et-le-Py", +"Pégairolles-de-Buèges", +"Pégairolles-de-l'Escalette", +"Péret-Bel-Air", +"Périers-en-Auge", +"Périers-sur-le-Dan", +"Pérignat-lès-Sarliève", +"Pérignat-sur-Allier", +"Pérignat-ès-Allier", +"Périgny-la-Rose", +"Pérols-sur-Vézère", +"Péronne-en-Mélantois", +"Péronnes-lez-Antoing", +"Péroy-les-Gombries", +"Pétrus-Colien", +"Pétrus-Colienne", +"Pétrus-Coliennes", +"Pétrus-Coliens", +"Pézilla-de-Conflent", +"Pézilla-la-Rivière", +"Pézènes-les-Mines", "Qo'noS", -"quad-core", -"quad-cores", -"quadri-accélération", -"quadri-accélérationnellement", -"quadri-ailé", -"quadri-couche", -"quadri-couches", -"quadri-courant", -"quadri-dimensionnel", -"quadri-dimensionnelle", -"quadri-dimensionnelles", -"quadri-dimensionnels", -"quadri-rotor", -"quadri-rotors", -"quadruple-croche", -"quadruples-croches", "Quaix-en-Chartreuse", -"quant-à-moi", -"quant-à-soi", -"quarante-cinq", -"quarante-deux", -"quarante-douze", -"quarante-et-un", -"quarante-et-une", -"quarante-huit", -"quarante-huitard", -"quarante-huitarde", -"quarante-huitardes", -"quarante-huitards", -"quarante-huitième", -"quarante-huitièmes", -"quarante-langues", -"quarante-neuf", -"quarante-neuvième", -"quarante-neuvièmes", -"quarante-quatre", -"quarante-sept", -"quarante-six", -"quarante-trois", -"quarante-vingt", "Quarré-les-Tombes", -"quart-arrière", -"quart-biscuité", -"quart-de-cercle", -"quart-de-finaliste", -"quart-de-finalistes", -"quart-de-pouce", -"quart-d'heure", -"quarte-fagot", -"quartier-général", -"quartier-maitre", -"quartier-maître", -"quartier-maitres", -"quartier-mestre", -"quartiers-maîtres", -"quart-monde", -"quarts-arrières", -"quarts-de-cercle", -"quart-temps", -"quatorze-marsiste", -"quatorze-marsistes", -"quatre-cent-vingt-et-un", "Quatre-Champs", -"quatre-chevaux", -"quatre-cinq-un", -"quatre-cornes", -"quatre-de-chiffre", -"quatre-épées", -"quatre-épices", -"quatre-feuilles", -"quatre-heura", -"quatre-heurai", -"quatre-heuraient", -"quatre-heurais", -"quatre-heurait", -"quatre-heurâmes", -"quatre-heurant", -"quatre-heuras", -"quatre-heurasse", -"quatre-heurassent", -"quatre-heurasses", -"quatre-heurassiez", -"quatre-heurassions", -"quatre-heurât", -"quatre-heurâtes", -"quatre-heure", -"quatre-heuré", -"quatre-heurent", -"quatre-heurer", -"quatre-heurera", -"quatre-heurerai", -"quatre-heureraient", -"quatre-heurerais", -"quatre-heurerait", -"quatre-heureras", -"quatre-heurèrent", -"quatre-heurerez", -"quatre-heureriez", -"quatre-heurerions", -"quatre-heurerons", -"quatre-heureront", -"quatre-heures", -"quatre-heurez", -"quatre-heuriez", -"quatre-heurions", -"quatre-heurons", -"quatre-huit", -"quatre-mâts", "Quatre-Nations", -"quatre-œil", -"quatre-pieds", -"quatre-quart", -"quatre-quarts", -"quatre-quatre", -"quatre-quatre-deux", -"quatre-quint", -"quatre-quints", -"quatre-quinze", -"quatre-quinzes", -"quatre-routois", "Quatre-Routois", -"quatre-routoise", "Quatre-Routoise", -"quatre-routoises", "Quatre-Routoises", -"quatre-saisons", -"quatres-de-chiffre", -"quatre-temps", -"quatre-trois-trois", -"quatre-vingt", -"quatre-vingtaine", -"quatre-vingtaines", -"quatre-vingt-cinq", -"quatre-vingt-deux", -"quatre-vingt-dix", -"quatre-vingt-dix-huit", -"quatre-vingt-dixième", -"quatre-vingt-dixièmes", -"quatre-vingt-dix-neuf", -"quatre-vingt-dix-neuvième", -"quatre-vingt-dix-neuvièmes", -"quatre-vingt-dix-sept", -"quatre-vingt-dizaine", -"quatre-vingt-dizaines", -"quatre-vingt-douze", -"quatre-vingt-huit", -"quatre-vingtième", -"quatre-vingtièmes", -"quatre-vingt-neuf", -"quatre-vingt-onze", -"quatre-vingt-quatorze", -"quatre-vingt-quatre", -"quatre-vingt-quinze", -"quatre-vingts", -"quatre-vingt-seize", -"quatre-vingt-sept", -"quatre-vingt-six", -"quatre-vingt-treize", -"quatre-vingt-trois", -"quatre-vingt-un", -"quatre-vingt-une", -"quat'z'arts", "Quelaines-Saint-Gault", -"quelques-unes", -"quelques-uns", -"quelqu'un", -"quelqu'une", "Quemigny-Poisot", "Quemigny-sur-Seine", "Quemper-Guézennec", -"que'ques", "Quesnay-Guesnon", "Quesnoy-le-Montant", "Quesnoy-sur-Airaines", "Quesnoy-sur-Deûle", -"questche-wasser", -"question-piège", -"questions-pièges", -"questions-réponses", -"questions-tags", -"question-tag", "Quet-en-Beaumont", "Quettreville-sur-Sienne", -"queue-d'aronde", -"queue-de-carpe", -"queue-de-chat", -"queue-de-cheval", -"queue-de-cochon", -"queue-de-lion", -"queue-de-loup", -"queue-de-morue", -"queue-de-paon", -"queue-de-pie", -"queue-de-poêle", -"queue-de-poireau", -"queue-de-porc", -"queue-de-pourceau", -"queue-de-rat", -"queue-de-renard", -"queue-de-scorpion", -"queue-de-souris", -"queue-de-vache", -"queue-d'hironde", -"queue-d'oison", -"queue-d'or", "Queue-du-Bois", -"queue-du-chat", -"queue-fourchue", -"queue-rouge", -"queues-d'aronde", -"queues-de-chat", -"queues-de-cheval", -"queues-de-cochon", -"queues-de-morue", -"queues-de-pie", -"queues-de-poêle", -"queues-de-pourceau", -"queues-de-rat", -"queues-de-renard", -"queues-de-vache", -"queues-d'hironde", -"queues-d'or", -"Quévreville-la-Poterie", -"Quévy-le-Grand", -"Quévy-le-Petit", "Queyssac-les-Vignes", -"quick-and-dirty", "Quiers-sur-Bézonde", -"Quiéry-la-Motte", "Quillebeuf-sur-Seine", "Quincampoix-Fleuzy", "Quincié-en-Beaujolais", "Quincy-Basse", "Quincy-Landzécourt", -"Quincy-le-Vicomte", -"Quincy-sous-le-Mont", -"Quincy-sous-Sénart", "Quincy-Voisins", -"qu-in-situ", +"Quincy-le-Vicomte", +"Quincy-sous-Sénart", +"Quincy-sous-le-Mont", "Quint-Fonsegrives", -"quintuple-croche", -"quintuples-croches", -"quinze-vingt", -"quinze-vingts", "Quiry-le-Sec", -"qui-va-là", -"qui-vive", -"quizalofop-éthyl", -"quizalofop-p-éthyl", -"quizalofop-P-éthyl", +"Quiéry-la-Motte", +"Quœux-Haut-Maînil", +"Quévreville-la-Poterie", +"Quévy-le-Grand", +"Quévy-le-Petit", "Quœux-Haut-Maînil", -"quote-part", -"quotes-parts", "Qwa-Qwa", +"R'n'B", +"R.-V.", +"RS-232", "Raa-Besenbek", "Rabastens-de-Bigorre", -"rabat-eau", -"rabat-eaux", -"rabat-joie", -"rabat-joies", "Rabat-les-Trois-Seigneurs", "Rabenkirchen-Faulück", -"rabi'-oul-aououal", -"rabi'-out-tani", "Rablay-sur-Layon", -"Rachecourt-sur-Marne", "Rachecourt-Suzémont", -"racine-blanche", -"racines-blanches", -"radars-tronçons", -"radar-tronçon", +"Rachecourt-sur-Marne", "Raddon-et-Chapendu", -"radicale-socialiste", -"radicales-socialistes", -"radical-socialisme", -"radical-socialismes", -"radical-socialiste", -"radicaux-socialistes", "Radinghem-en-Weppes", -"radio-actinium", -"radio-activité", -"radio-activités", -"radio-amateur", -"radio-amateurs", -"radio-canadien", -"radio-carpien", -"radio-carpienne", -"radio-carpiennes", -"radio-carpiens", -"radio-crochet", -"radio-crochets", -"radio-cubital", -"radio-diffusion", -"radio-étiquette", -"radio-étiquettes", -"radio-gramophone", -"radio-gramophones", -"radio-identification", -"radio-identifications", -"radio-interféromètre", -"radio-interféromètres", -"radio-isotope", -"radio-isotopes", -"radio-opacité", -"radio-opacités", -"radio-palmaire", -"radio-phonographe", -"radio-phonographes", -"radio-réalité", -"radio-réalités", -"radio-réveil", -"radio-taxi", -"radio-télévisé", -"radio-télévisée", -"radio-télévisées", -"radio-télévisés", -"radio-télévision", -"radio-télévisions", -"radio-thorium", -"rad-soc", -"rad'soc", -"rad-socs", -"rad'socs", "Ragow-Merz", -"rag-time", -"rag-times", "Raguhn-Jeßnitz", -"rahat-lokoum", -"rahat-lokoums", -"rahat-loukoum", -"rahat-loukoums", -"raid-aventure", -"rai-de-coeur", -"rai-de-cœur", -"raie-aigle", -"raie-guitare", -"raie-papillon", -"raies-aigles", -"raies-papillons", "Raillencourt-Sainte-Olle", -"rail-road", -"rail-route", -"Raï'n'B", -"rais-de-coeur", -"rais-de-cœur", -"raisin-de-chien", -"raisins-de-chien", "Raissac-d'Aude", "Raissac-sur-Lampy", "Ralbitz-Rosenthal", -"ralé-poussé", -"râlé-poussé", -"Râlé-Poussé", -"rallie-papier", -"rallonge-bouton", -"rallonge-boutons", -"ramasse-bourrier", -"ramasse-bourriers", -"ramasse-couvert", -"ramasse-couverts", -"ramasse-miette", -"ramasse-miettes", -"ramasse-monnaie", -"ramasse-poussière", -"ramasse-poussières", -"ramasse-ton-bras", -"ramasseuse-presse", -"ramasseuses-presses", "Rambluzin-et-Benoite-Vaux", "Ramegnies-Chin", "Ramillies-Offus", "Ramonville-Saint-Agne", -"(R)-amphétamine", "Ramstein-Miesenbach", "Rancourt-sur-Ornain", "Rang-du-Fliers", @@ -20370,283 +11075,45 @@ FR_BASE_EXCEPTIONS = [ "Ranspach-le-Haut", "Ranville-Breuillaud", "Raon-aux-Bois", -"Raon-lès-Leau", "Raon-l'Etape", "Raon-l'Étape", +"Raon-lès-Leau", "Raon-sur-Plaine", "Rapide-Danseurois", "Rapperswil-Jona", "Raschau-Markersbach", -"ras-de-cou", -"rase-motte", -"rase-mottes", -"rase-pet", -"rase-pets", -"ras-la-moule", -"ras-le-bol", -"ras-le-bonbon", -"ras-le-cresson", -"ras-les-fesses", -"rat-baillet", -"rat-bayard", -"rat-de-cave", -"rat-garou", -"ratisse-caisse", -"rats-de-cave", -"rats-garous", -"rat-taupe", -"rat-trompette", "Raucourt-au-Bois", "Raucourt-et-Flaba", "Rauville-la-Bigot", "Rauville-la-Place", "Ravel-et-Ferriers", "Raville-sur-Sânon", -"Raye-sur-Authie", -"ray-grass", -"Rayol-Canadel-sur-Mer", "Ray-sur-Saône", -"Razac-de-Saussignac", +"Raye-sur-Authie", +"Rayol-Canadel-sur-Mer", "Razac-d'Eymet", +"Razac-de-Saussignac", "Razac-sur-l'Isle", -"raz-de-marée", -"ready-made", -"reality-show", -"reality-shows", -"réal-politique", -"réal-politiques", -"réarc-bouta", -"réarc-boutai", -"réarc-boutaient", -"réarc-boutais", -"réarc-boutait", -"réarc-boutâmes", -"réarc-boutant", -"réarc-boutas", -"réarc-boutasse", -"réarc-boutassent", -"réarc-boutasses", -"réarc-boutassiez", -"réarc-boutassions", -"réarc-boutât", -"réarc-boutâtes", -"réarc-boute", -"réarc-bouté", -"réarc-boutée", -"réarc-boutées", -"réarc-boutent", -"réarc-bouter", -"réarc-boutera", -"réarc-bouterai", -"réarc-bouteraient", -"réarc-bouterais", -"réarc-bouterait", -"réarc-bouteras", -"réarc-boutèrent", -"réarc-bouterez", -"réarc-bouteriez", -"réarc-bouterions", -"réarc-bouterons", -"réarc-bouteront", -"réarc-boutes", -"réarc-boutés", -"réarc-boutez", -"réarc-boutiez", -"réarc-boutions", -"réarc-boutons", -"Réaup-Lisse", +"Raï'n'B", "Rebecq-Rognon", "Rebreuve-Ranchicourt", "Rebreuve-sur-Canche", -"rebrousse-poil", -"réception-cadeaux", "Recey-sur-Ource", "Rechenberg-Bienenmühle", -"Réchicourt-la-Petite", -"Réchicourt-le-Château", -"récipient-mesure", -"récipient-mesures", "Reckange-sur-Mess", "Reckingen-Gluringen", "Recologne-lès-Rioz", "Recoubeau-Jansac", +"Recoules-Prévinquières", "Recoules-d'Aubrac", "Recoules-de-Fumas", -"Recoules-Prévinquières", -"recourbe-cils", -"Récourt-le-Creux", "Recques-sur-Course", "Recques-sur-Hem", -"recto-vaginal", -"recto-verso", -"redouble-cliqua", -"redouble-cliquai", -"redouble-cliquaient", -"redouble-cliquais", -"redouble-cliquait", -"redouble-cliquâmes", -"redouble-cliquant", -"redouble-cliquas", -"redouble-cliquasse", -"redouble-cliquassent", -"redouble-cliquasses", -"redouble-cliquassiez", -"redouble-cliquassions", -"redouble-cliquât", -"redouble-cliquâtes", -"redouble-clique", -"redouble-cliqué", -"redouble-cliquent", -"redouble-cliquer", -"redouble-cliquera", -"redouble-cliquerai", -"redouble-cliqueraient", -"redouble-cliquerais", -"redouble-cliquerait", -"redouble-cliqueras", -"redouble-cliquèrent", -"redouble-cliquerez", -"redouble-cliqueriez", -"redouble-cliquerions", -"redouble-cliquerons", -"redouble-cliqueront", -"redouble-cliques", -"redouble-cliquez", -"redouble-cliquiez", -"redouble-cliquions", -"redouble-cliquons", -"redresse-seins", -"re'em", -"re'ems", -"réentr'apercevaient", -"réentr'apercevais", -"réentr'apercevait", -"réentr'apercevant", -"réentr'apercevez", -"réentr'aperceviez", -"réentr'apercevions", -"réentr'apercevoir", -"réentr'apercevons", -"réentr'apercevra", -"réentr'apercevrai", -"réentr'apercevraient", -"réentr'apercevrais", -"réentr'apercevrait", -"réentr'apercevras", -"réentr'apercevrez", -"réentr'apercevriez", -"réentr'apercevrions", -"réentr'apercevrons", -"réentr'apercevront", -"réentr'aperçois", -"réentr'aperçoit", -"réentr'aperçoive", -"réentr'aperçoivent", -"réentr'aperçoives", -"réentr'aperçu", -"réentr'aperçue", -"réentr'aperçues", -"réentr'aperçûmes", -"réentr'aperçurent", -"réentr'aperçus", -"réentr'aperçusse", -"réentr'aperçussent", -"réentr'aperçusses", -"réentr'aperçussiez", -"réentr'aperçussions", -"réentr'aperçut", -"réentr'aperçût", -"réentr'aperçûtes", -"réentr'ouvert", -"réentr'ouverte", -"réentr'ouvertes", -"réentr'ouverts", -"réentr'ouvraient", -"réentr'ouvrais", -"réentr'ouvrait", -"réentr'ouvrant", -"réentr'ouvre", -"réentr'ouvrent", -"réentr'ouvres", -"réentr'ouvrez", -"réentr'ouvriez", -"réentr'ouvrîmes", -"réentr'ouvrions", -"réentr'ouvrir", -"réentr'ouvrira", -"réentr'ouvrirai", -"réentr'ouvriraient", -"réentr'ouvrirais", -"réentr'ouvrirait", -"réentr'ouvriras", -"réentr'ouvrirent", -"réentr'ouvrirez", -"réentr'ouvririez", -"réentr'ouvririons", -"réentr'ouvrirons", -"réentr'ouvriront", -"réentr'ouvris", -"réentr'ouvrisse", -"réentr'ouvrissent", -"réentr'ouvrisses", -"réentr'ouvrissiez", -"réentr'ouvrissions", -"réentr'ouvrit", -"réentr'ouvrît", -"réentr'ouvrîtes", -"réentr'ouvrons", -"Réez-Fosse-Martin", -"refox-trotta", -"refox-trottai", -"refox-trottaient", -"refox-trottais", -"refox-trottait", -"refox-trottâmes", -"refox-trottant", -"refox-trottas", -"refox-trottasse", -"refox-trottassent", -"refox-trottasses", -"refox-trottassiez", -"refox-trottassions", -"refox-trottât", -"refox-trottâtes", -"refox-trotte", -"refox-trotté", -"refox-trottent", -"refox-trotter", -"refox-trottera", -"refox-trotterai", -"refox-trotteraient", -"refox-trotterais", -"refox-trotterait", -"refox-trotteras", -"refox-trottèrent", -"refox-trotterez", -"refox-trotteriez", -"refox-trotterions", -"refox-trotterons", -"refox-trotteront", -"refox-trottes", -"refox-trottez", -"refox-trottiez", -"refox-trottions", -"refox-trottons", -"regardez-moi", -"régis-borgien", -"Régis-Borgien", -"régis-borgienne", -"Régis-Borgienne", -"régis-borgiennes", -"Régis-Borgiennes", -"régis-borgiens", -"Régis-Borgiens", "Regis-Breitingen", -"Regnéville-sur-Mer", -"Regnéville-sur-Meuse", -"Régnié-Durette", "Regnière-Ecluse", "Regnière-Écluse", +"Regnéville-sur-Mer", +"Regnéville-sur-Meuse", "Rehburg-Loccum", "Rehlingen-Siersburg", "Rehm-Flehde-Bargen", @@ -20657,370 +11124,44 @@ FR_BASE_EXCEPTIONS = [ "Reignier-Esery", "Reignier-Ésery", "Reims-la-Brûlée", -"reine-claude", -"reine-des-bois", -"reine-des-prés", -"reine-marguerite", -"reines-claudes", -"reines-des-bois", -"reines-des-prés", -"reines-marguerites", "Reinhardtsdorf-Schöna", "Rejet-de-Beaulieu", -"relève-gravure", -"relève-gravures", -"relève-moustache", -"relève-moustaches", -"relève-quartier", -"relève-quartiers", -"relève-selle", -"relève-selles", -"Rémalard-en-Perche", "Rembercourt-Sommaisne", "Rembercourt-sur-Mad", "Remda-Teichel", -"Rémering-lès-Hargarten", -"Rémering-lès-Puttelange", -"remettez-vous", -"remicro-onda", -"remicro-ondai", -"remicro-ondaient", -"remicro-ondais", -"remicro-ondait", -"remicro-ondâmes", -"remicro-ondant", -"remicro-ondas", -"remicro-ondasse", -"remicro-ondassent", -"remicro-ondasses", -"remicro-ondassiez", -"remicro-ondassions", -"remicro-ondât", -"remicro-ondâtes", -"remicro-onde", -"remicro-ondé", -"remicro-ondée", -"remicro-ondées", -"remicro-ondent", -"remicro-onder", -"remicro-ondera", -"remicro-onderai", -"remicro-onderaient", -"remicro-onderais", -"remicro-onderait", -"remicro-onderas", -"remicro-ondèrent", -"remicro-onderez", -"remicro-onderiez", -"remicro-onderions", -"remicro-onderons", -"remicro-onderont", -"remicro-ondes", -"remicro-ondés", -"remicro-ondez", -"remicro-ondiez", -"remicro-ondions", -"remicro-ondons", "Remilly-Aillicourt", +"Remilly-Wirquin", +"Remilly-Wirquinois", +"Remilly-Wirquinoise", +"Remilly-Wirquinoises", "Remilly-en-Montagne", "Remilly-les-Pothées", "Remilly-sur-Lozon", "Remilly-sur-Tille", -"Remilly-Wirquin", -"remilly-wirquinois", -"Remilly-Wirquinois", -"remilly-wirquinoise", -"Remilly-Wirquinoise", -"remilly-wirquinoises", -"Remilly-Wirquinoises", "Remire-Montjoly", -"Rémondans-Vaivre", -"remonte-pente", -"remonte-pentes", "Remoray-Boujeons", "Rems-Murr", -"remue-ménage", -"remue-ménages", -"remue-méninge", -"remue-méninges", -"remue-queue", -"remue-queues", -"rémy-montais", -"Rémy-Montais", -"rémy-montaise", -"Rémy-Montaise", -"rémy-montaises", -"Rémy-Montaises", -"renarde-garou", -"renard-garou", -"rendez-vous", -"r'endormaient", -"r'endormais", -"r'endormait", -"r'endormant", -"r'endorme", -"r'endorment", -"r'endormes", -"r'endormez", -"r'endormi", -"r'endormie", -"r'endormies", -"r'endormiez", -"r'endormîmes", -"r'endormions", -"r'endormir", -"r'endormira", -"r'endormirai", -"r'endormiraient", -"r'endormirais", -"r'endormirait", -"r'endormiras", -"r'endormirent", -"r'endormirez", -"r'endormiriez", -"r'endormirions", -"r'endormirons", -"r'endormiront", -"r'endormis", -"r'endormisse", -"r'endormissent", -"r'endormisses", -"r'endormissiez", -"r'endormissions", -"r'endormit", -"r'endormît", -"r'endormîtes", -"r'endormons", -"r'endors", -"r'endort", "Rendsburg-Eckernförde", "Rennes-en-Grenouilles", "Rennes-le-Château", "Rennes-les-Bains", -"rennes-robots", "Rennes-sur-Loue", -"renouée-bambou", -"rentre-dedans", -"rentr'ouvert", -"rentr'ouverte", -"rentr'ouvertes", -"rentr'ouverts", -"rentr'ouvraient", -"rentr'ouvrais", -"rentr'ouvrait", -"rentr'ouvrant", -"rentr'ouvre", -"rentr'ouvrent", -"rentr'ouvres", -"rentr'ouvrez", -"rentr'ouvriez", -"rentr'ouvrîmes", -"rentr'ouvrions", -"rentr'ouvrir", -"rentr'ouvrira", -"rentr'ouvrirai", -"rentr'ouvriraient", -"rentr'ouvrirais", -"rentr'ouvrirait", -"rentr'ouvriras", -"rentr'ouvrirent", -"rentr'ouvrirez", -"rentr'ouvririez", -"rentr'ouvririons", -"rentr'ouvrirons", -"rentr'ouvriront", -"rentr'ouvris", -"rentr'ouvrisse", -"rentr'ouvrissent", -"rentr'ouvrisses", -"rentr'ouvrissiez", -"rentr'ouvrissions", -"rentr'ouvrit", -"rentr'ouvrît", -"rentr'ouvrîtes", -"rentr'ouvrons", -"renvoi-instruire", -"repetit-déjeuna", -"repetit-déjeunai", -"repetit-déjeunaient", -"repetit-déjeunais", -"repetit-déjeunait", -"repetit-déjeunâmes", -"repetit-déjeunant", -"repetit-déjeunas", -"repetit-déjeunasse", -"repetit-déjeunassent", -"repetit-déjeunasses", -"repetit-déjeunassiez", -"repetit-déjeunassions", -"repetit-déjeunât", -"repetit-déjeunâtes", -"repetit-déjeune", -"repetit-déjeuné", -"repetit-déjeunent", -"repetit-déjeuner", -"repetit-déjeunera", -"repetit-déjeunerai", -"repetit-déjeuneraient", -"repetit-déjeunerais", -"repetit-déjeunerait", -"repetit-déjeuneras", -"repetit-déjeunèrent", -"repetit-déjeunerez", -"repetit-déjeuneriez", -"repetit-déjeunerions", -"repetit-déjeunerons", -"repetit-déjeuneront", -"repetit-déjeunes", -"repetit-déjeunez", -"repetit-déjeuniez", -"repetit-déjeunions", -"repetit-déjeunons", -"repique-niqua", -"repique-niquai", -"repique-niquaient", -"repique-niquais", -"repique-niquait", -"repique-niquâmes", -"repique-niquant", -"repique-niquas", -"repique-niquasse", -"repique-niquassent", -"repique-niquasses", -"repique-niquassiez", -"repique-niquassions", -"repique-niquât", -"repique-niquâtes", -"repique-nique", -"repique-niqué", -"repique-niquent", -"repique-niquer", -"repique-niquera", -"repique-niquerai", -"repique-niqueraient", -"repique-niquerais", -"repique-niquerait", -"repique-niqueras", -"repique-niquèrent", -"repique-niquerez", -"repique-niqueriez", -"repique-niquerions", -"repique-niquerons", -"repique-niqueront", -"repique-niques", -"repique-niquez", -"repique-niquiez", -"repique-niquions", -"repique-niquons", -"répondeur-enregistreur", -"répondeur-enregistreurs", -"repose-pied", -"repose-pieds", -"repose-poignet", -"repose-poignets", -"repose-tête", -"repose-têtes", -"requin-baleine", -"requin-chabot", -"requin-chat", -"requin-chats", -"requin-citron", -"requin-corail", -"requin-crocodile", -"requin-garou", -"requin-griset", -"requin-hâ", -"requin-maquereau", -"requin-marteau", -"requin-nourrice", -"requin-renard", -"requins-baleines", -"requins-citrons", -"requins-crocodiles", -"requins-garous", -"requins-hâ", -"requins-marteaux", -"requins-taupes", -"requins-tigres", -"requin-taupe", -"requin-taureau", -"requin-tigre", -"requin-vache", -"requin-zèbre", -"r'es", -"résino-gommeux", "Ressons-l'Abbaye", "Ressons-le-Long", "Ressons-sur-Matz", -"r'est", -"restaurant-bar", -"restaurant-bistro", -"restaurant-brasserie", -"restaurant-pub", -"restaurants-bistros", -"reste-avec", -"resto-bar", -"resto-bistro", -"resto-brasserie", -"rest-o-pack", -"resto-pub", -"r'étaient", -"r'étais", -"r'était", -"r'étant", -"r'été", -"r'êtes", -"r'étiez", -"r'étions", -"retraite-chapeau", -"retraites-chapeaux", -"r'être", -"retroussons-nos-manches", "Reuil-en-Brie", -"Reuilly-Sauvigny", "Reuil-sur-Brêche", +"Reuilly-Sauvigny", "Reulle-Vergy", -"réunion-bilan", -"réunions-bilan", -"rêve-creux", -"réveille-matin", -"réveille-matins", -"réveil-matin", "Revel-Tourdan", -"revenant-bon", -"revenants-bons", -"revenez-y", "Reventin-Vaugris", +"Revest-Saint-Martin", "Revest-des-Brousses", "Revest-du-Bion", "Revest-les-Roches", -"Revest-Saint-Martin", "Revigny-sur-Ornain", -"Réville-aux-Bois", -"rex-castor", -"rex-castors", -"rez-de-chaussée", -"rez-de-cour", -"rez-de-jardin", -"rez-mur", "Rheda-Wiedenbrück", "Rheingau-Taunus", -"Rhêmes-Notre-Dame", -"Rhêmes-Saint-Georges", -"Rhénanie-du-Nord-Westphalie", -"Rhénanie-Palatinat", -"rhéo-épaississant", -"rhéo-épaississante", -"rhéo-épaississantes", -"rhéo-épaississants", -"rhéo-fluidifiant", -"rhéo-fluidifiante", -"rhéo-fluidifiantes", -"rhéo-fluidifiants", -"rhéto-roman", -"rhéto-romane", -"rhéto-romanes", -"rhéto-romans", "Rhin-Berg", "Rhin-Erft", "Rhin-Hunsrück", @@ -21028,72 +11169,31 @@ FR_BASE_EXCEPTIONS = [ "Rhin-Neckar", "Rhin-Palatinat", "Rhin-Sieg", -"Rhode-Sainte-Agathe", "Rhode-Saint-Genèse", "Rhode-Saint-Pierre", -"rhodesian-ridgeback", +"Rhode-Sainte-Agathe", +"Rhénanie-Palatinat", +"Rhénanie-du-Nord-Westphalie", +"Rhêmes-Notre-Dame", +"Rhêmes-Saint-Georges", "Rhône-Alpes", "Rhön-Grabfeld", "Ria-Sirach", -"ria-sirachois", "Ria-Sirachois", -"ria-sirachoise", "Ria-Sirachoise", -"ria-sirachoises", "Ria-Sirachoises", "Ribaute-les-Tavernes", -"Ribécourt-Dreslincourt", -"Ribécourt-la-Tour", "Ribemont-sur-Ancre", "Ribnitz-Damgarten", -"ric-à-rac", +"Ribécourt-Dreslincourt", +"Ribécourt-la-Tour", "Ricarville-du-Val", "Richebourg-Saint-Vaast", "Richelieu-Yamaskois", -"rick-rolla", -"rick-rollai", -"rick-rollaient", -"rick-rollais", -"rick-rollait", -"rick-rollâmes", -"rick-rollant", -"rick-rollas", -"rick-rollasse", -"rick-rollassent", -"rick-rollasses", -"rick-rollassiez", -"rick-rollassions", -"rick-rollât", -"rick-rollâtes", -"rick-rolle", -"rick-rollé", -"rick-rollée", -"rick-rollées", -"rick-rollent", -"rick-roller", -"rick-rollera", -"rick-rollerai", -"rick-rolleraient", -"rick-rollerais", -"rick-rollerait", -"rick-rolleras", -"rick-rollèrent", -"rick-rollerez", -"rick-rolleriez", -"rick-rollerions", -"rick-rollerons", -"rick-rolleront", -"rick-rolles", -"rick-rollés", -"rick-rollez", -"rick-rolliez", -"rick-rollions", -"rick-rollons", -"ric-rac", "Riec-sur-Bélon", "Ried-Brig", -"Rielasingen-Worblingen", "Riel-les-Eaux", +"Rielasingen-Worblingen", "Riencourt-lès-Bapaume", "Riencourt-lès-Cagnicourt", "Rieschweiler-Mühlbach", @@ -21101,42 +11201,31 @@ FR_BASE_EXCEPTIONS = [ "Rietz-Neuendorf", "Rietzneuendorf-Staakow", "Rieutort-de-Randon", +"Rieux-Minervois", +"Rieux-Volvestre", "Rieux-de-Pelleport", "Rieux-en-Cambrésis", "Rieux-en-Val", -"rieux-en-valois", "Rieux-en-Valois", -"rieux-en-valoise", "Rieux-en-Valoise", -"rieux-en-valoises", "Rieux-en-Valoises", -"Rieux-Minervois", -"Rieux-Volvestre", -"rigaud-montain", +"Rig-Véda", "Rigaud-Montain", -"rigaud-montaine", "Rigaud-Montaine", -"rigaud-montaines", "Rigaud-Montaines", -"rigaud-montains", "Rigaud-Montains", "Rigil-K", "Rignieux-le-Franc", +"Rigny-Saint-Martin", +"Rigny-Ussé", +"Rigny-Usséen", +"Rigny-Usséenne", +"Rigny-Usséennes", +"Rigny-Usséens", "Rigny-la-Nonneuse", "Rigny-la-Salle", "Rigny-le-Ferron", -"Rigny-Saint-Martin", "Rigny-sur-Arroux", -"Rigny-Ussé", -"rigny-usséen", -"Rigny-Usséen", -"rigny-usséenne", -"Rigny-Usséenne", -"rigny-usséennes", -"Rigny-Usséennes", -"rigny-usséens", -"Rigny-Usséens", -"Rig-Véda", "Rijssen-Holten", "Rilhac-Lastours", "Rilhac-Rancon", @@ -21144,9 +11233,9 @@ FR_BASE_EXCEPTIONS = [ "Rilhac-Xaintrie", "Rilland-Bath", "Rillieux-la-Pape", +"Rilly-Sainte-Syre", "Rilly-aux-Oies", "Rilly-la-Montagne", -"Rilly-Sainte-Syre", "Rilly-sur-Aisne", "Rilly-sur-Loire", "Rilly-sur-Vienne", @@ -21154,159 +11243,105 @@ FR_BASE_EXCEPTIONS = [ "Rimbach-près-Masevaux", "Rimbez-et-Baudiets", "Rimon-et-Savel", -"rince-bouche", -"rince-bouches", -"rince-bouteille", -"rince-bouteilles", -"rince-doigt", -"rince-doigts", -"Riom-ès-Montagnes", "Riom-Parsonz", +"Riom-ès-Montagnes", "Rion-des-Landes", "Rioux-Martin", -"Risch-Rotkreuz", "Ris-Orangis", -"risque-tout", +"Risch-Rotkreuz", "Risum-Lindholm", "Rivas-Vaciamadrid", -"Rive-de-Gier", -"Rivedoux-Plage", "Rive-Nord", -"Rives-en-Seine", "Rive-Sud", "Rive-Sudois", +"Rive-de-Gier", +"Rivedoux-Plage", +"Rives d'Andaine", +"Rives de l'Yon", +"Rives-en-Seine", "Riviera-Pays-d'Enhaut", "Rivière-Devant", -"Rivière-du-Loup", -"Rivière-les-Fosses", "Rivière-Pilote", "Rivière-Saas-et-Gourby", "Rivière-Salée", -"Rivières-le-Bois", +"Rivière-du-Loup", +"Rivière-les-Fosses", "Rivière-sur-Tarn", +"Rivières-le-Bois", "Rizaucourt-Buchey", -"riz-pain-sel", -"R'n'B", -"road-book", -"road-books", +"Ro-Ro", +"Ro-Ros", "Roannes-Saint-Mary", -"roast-beef", -"roast-beefs", -"robe-chandail", -"robe-housse", "Robert-Espagne", -"robert-le-diable", "Robert-Magny", -"robert-messin", "Robert-Messin", -"robert-messine", "Robert-Messine", -"robert-messines", "Robert-Messines", -"robert-messins", "Robert-Messins", -"robes-chandails", -"robes-housses", "Robiac-Rochessadoule", "Robleda-Cervantes", -"robot-chien", -"robots-chiens", -"roche-blanchais", +"Roc-Libre", "Roche-Blanchais", -"roche-blanchaise", "Roche-Blanchaise", -"roche-blanchaises", "Roche-Blanchaises", "Roche-Charles-la-Mayrand", +"Roche-Saint-Secret-Béconne", "Roche-d'Agoux", "Roche-en-Régnier", "Roche-et-Méry", "Roche-et-Raucourt", +"Roche-la-Molière", +"Roche-le-Peyroux", +"Roche-lez-Beaupré", +"Roche-lès-Clerval", +"Roche-sur-Linotte-et-Sorans-les-Cordiers", +"Rochefort-Montagne", +"Rochefort-Samson", "Rochefort-du-Gard", "Rochefort-en-Terre", "Rochefort-en-Valdaine", "Rochefort-en-Yvelines", -"Rochefort-Montagne", -"Rochefort-Samson", "Rochefort-sur-Brévon", -"Rochefort-sur-la-Côte", "Rochefort-sur-Loire", "Rochefort-sur-Nenon", -"Roche-la-Molière", -"Roche-le-Peyroux", -"Roche-lès-Clerval", -"Roche-lez-Beaupré", -"roche-mère", -"roche-papier-ciseaux", -"Roche-Saint-Secret-Béconne", +"Rochefort-sur-la-Côte", "Roches-Bettaincourt", -"Roches-lès-Blamont", -"roches-mères", "Roches-Prémarie-Andillé", +"Roches-lès-Blamont", "Roches-sur-Marne", "Roches-sur-Rognon", -"Roche-sur-Linotte-et-Sorans-les-Cordiers", "Rochetaillée-sur-Saône", "Rochy-Condé", -"rock-a-billy", -"rocking-chair", -"rocking-chairs", -"rock'n'roll", "Roclenge-Looz", "Roclenge-sur-Geer", -"Roc-Libre", "Rocourt-Saint-Martin", "Rocquigny-la-Hardoye", "Rodengo-Saiano", -"Rödersheim-Gronau", "Roesbrugge-Haringe", -"Roézé-sur-Sarthe", -"roge-bougeron", "Roge-Bougeron", -"roge-bougeronne", "Roge-Bougeronne", -"roge-bougeronnes", "Roge-Bougeronnes", -"roge-bougerons", "Roge-Bougerons", -"roger-bontemps", -"rogne-cul", -"rogne-pied", -"rogne-pieds", -"rogne-salaires", "Rogny-les-Sept-Ecluses", "Rogny-les-Sept-Écluses", "Rohrbach-lès-Bitche", -"roi-de-rats", -"Roinville-sous-Auneau", -"rois-de-rats", "Roi-Soleil", +"Roinville-sous-Auneau", "Roissy-en-Brie", "Roissy-en-France", "Rollegem-Kapelle", "Rolleghem-Cappelle", -"roller-derby", -"roller-derbys", -"roll-out", -"roll-outs", -"Romagne-sous-les-Côtes", "Romagne-sous-Montfaucon", +"Romagne-sous-les-Côtes", "Romagny-Fontenay", "Romagny-sous-Rougemont", "Romain-aux-Bois", -"Romainmôtier-Envy", "Romain-sur-Meuse", -"Romanèche-Thorins", +"Romainmôtier-Envy", "Romanel-sur-Lausanne", "Romanel-sur-Morges", -"roman-feuilleton", -"roman-fleuve", -"roman-photo", -"roman-photos", -"romans-feuilletons", -"romans-fleuves", -"romans-photos", "Romans-sur-Isère", +"Romanèche-Thorins", "Rombach-le-Franc", "Rombies-et-Marchipont", "Romeny-sur-Marne", @@ -21315,67 +11350,14 @@ FR_BASE_EXCEPTIONS = [ "Romilly-sur-Andelle", "Romilly-sur-Seine", "Romorantin-Lanthenay", -"rompt-pierre", -"rompt-pierres", "Roncherolles-en-Bray", "Roncherolles-sur-le-Vivier", -"rond-de-cuir", -"ronde-bosse", -"ronde-bosses", -"rondes-bosses", -"rond-point", -"rond-ponna", -"rond-ponnai", -"rond-ponnaient", -"rond-ponnais", -"rond-ponnait", -"rond-ponnâmes", -"rond-ponnant", -"rond-ponnas", -"rond-ponnasse", -"rond-ponnassent", -"rond-ponnasses", -"rond-ponnassiez", -"rond-ponnassions", -"rond-ponnât", -"rond-ponnâtes", -"rond-ponne", -"rond-ponné", -"rond-ponnent", -"rond-ponner", -"rond-ponnera", -"rond-ponnerai", -"rond-ponneraient", -"rond-ponnerais", -"rond-ponnerait", -"rond-ponneras", -"rond-ponnèrent", -"rond-ponnerez", -"rond-ponneriez", -"rond-ponnerions", -"rond-ponnerons", -"rond-ponneront", -"rond-ponnes", -"rond-ponnez", -"rond-ponniez", -"rond-ponnions", -"rond-ponnons", -"ronds-de-cuir", -"ronds-points", -"ronge-bois", -"ronge-maille", -"rongo-rongo", -"ron-ron", "Ronzo-Chienis", -"Roôcourt-la-Côte", "Roodt-sur-Eisch", "Roodt-sur-Syre", "Roost-Warendin", -"roost-warendinois", "Roost-Warendinois", -"roost-warendinoise", "Roost-Warendinoise", -"roost-warendinoises", "Roost-Warendinoises", "Roquebrune-Cap-Martin", "Roquebrune-sur-Argens", @@ -21390,16 +11372,13 @@ FR_BASE_EXCEPTIONS = [ "Roquelaure-Saint-Aubin", "Roquestéron-Grasse", "Rorbach-lès-Dieuze", -"Ro-Ro", -"Ro-Ros", "Rosay-sur-Lieure", -"rose-croix", -"rose-de-mer", "Rose-Marie", -"rose-marine", "Rosenthal-Bielatal", -"roses-marines", "Roset-Fluans", +"Rosiers-d'Egletons", +"Rosiers-d'Égletons", +"Rosiers-de-Juillac", "Rosières-aux-Salines", "Rosières-devant-Bar", "Rosières-en-Blois", @@ -21408,9 +11387,6 @@ FR_BASE_EXCEPTIONS = [ "Rosières-près-Troyes", "Rosières-sur-Barbèche", "Rosières-sur-Mance", -"Rosiers-d'Egletons", -"Rosiers-d'Égletons", -"Rosiers-de-Juillac", "Rosnay-l'Hôpital", "Rosny-sous-Bois", "Rosny-sur-Seine", @@ -21418,83 +11394,40 @@ FR_BASE_EXCEPTIONS = [ "Rosoy-en-Multien", "Rosoy-le-Vieil", "Rosoy-sur-Amance", -"rosti-montois", "Rosti-Montois", -"rosti-montoise", "Rosti-Montoise", -"rosti-montoises", "Rosti-Montoises", "Rotheux-Rimière", -"Rötsweiler-Nockenthal", "Rottach-Egern", "Rottal-Inn", +"Rou-Marson", "Rouessé-Fontaine", "Rouessé-Vassé", +"Rouffiac-Tolosan", "Rouffiac-d'Aude", "Rouffiac-des-Corbières", -"Rouffiac-Tolosan", -"Rouffignac-de-Sigoulès", "Rouffignac-Saint-Cernin-de-Reilhac", -"rouge-aile", -"rouge-bord", -"rouge-brun", -"rouge-flasher", -"rouge-gorge", -"rouge-herbe", -"rouge-herbes", -"Rougemont-le-Château", -"rouge-noir", +"Rouffignac-de-Sigoulès", "Rouge-Perriers", -"rouge-pie", -"rouge-queue", -"rouges-ailes", -"rouges-gorges", -"rouges-queues", -"rouget-barbet", -"rouget-grondin", +"Rougemont-le-Château", "Rouilly-Sacey", "Rouilly-Saint-Loup", -"roulage-décollage", -"roulé-boulé", -"roule-goupille", -"roule-goupilles", -"rouler-bouler", -"roulé-saucisse", -"roulés-boulés", -"roule-ta-bosse", "Roullet-Saint-Estèphe", -"roullet-stéphanois", "Roullet-Stéphanois", -"roullet-stéphanoise", "Roullet-Stéphanoise", -"roullet-stéphanoises", "Roullet-Stéphanoises", -"roul-sa-bosse", -"Rou-Marson", "Roumazières-Loubert", "Rouperroux-le-Coquet", "Rousseau-esque", "Rousseau-esques", -"rousses-têtes", -"rousse-tête", "Rousset-les-Vignes", "Roussillon-en-Morvan", "Roussy-le-Village", -"r'ouvert", -"r'ouverte", -"r'ouvertes", -"r'ouverts", -"r'ouvraient", -"r'ouvrais", -"r'ouvrait", -"r'ouvrant", "Rouvray-Catillon", "Rouvray-Saint-Denis", -"Rouvray-Sainte-Croix", "Rouvray-Saint-Florentin", -"r'ouvre", -"r'ouvrent", -"r'ouvres", +"Rouvray-Sainte-Croix", +"Rouvres-Saint-Jean", "Rouvres-en-Multien", "Rouvres-en-Plaine", "Rouvres-en-Woëvre", @@ -21502,81 +11435,52 @@ FR_BASE_EXCEPTIONS = [ "Rouvres-la-Chétive", "Rouvres-les-Bois", "Rouvres-les-Vignes", -"Rouvres-Saint-Jean", "Rouvres-sous-Meilly", "Rouvres-sur-Aube", -"r'ouvrez", -"r'ouvriez", -"r'ouvrîmes", -"r'ouvrions", -"r'ouvrir", -"r'ouvrira", -"r'ouvrirai", -"r'ouvriraient", -"r'ouvrirais", -"r'ouvrirait", -"r'ouvriras", -"r'ouvrirent", -"r'ouvrirez", -"r'ouvririez", -"r'ouvririons", -"r'ouvrirons", -"r'ouvriront", -"r'ouvris", -"r'ouvrisse", -"r'ouvrissent", -"r'ouvrisses", -"r'ouvrissiez", -"r'ouvrissions", -"r'ouvrit", -"r'ouvrît", -"r'ouvrîtes", "Rouvrois-sur-Meuse", "Rouvrois-sur-Othain", -"r'ouvrons", +"Rouvroy-Ripont", "Rouvroy-en-Santerre", "Rouvroy-les-Merles", "Rouvroy-les-Pothées", -"Rouvroy-Ripont", "Rouvroy-sur-Audry", "Rouvroy-sur-Marne", "Rouvroy-sur-Serre", -"Rouxmesnil-Bouteilles", -"roux-mirien", "Roux-Mirien", "Roux-Mirienne", "Roux-Miroir", +"Rouxmesnil-Bouteilles", "Rouy-le-Grand", "Rouy-le-Petit", "Rouziers-de-Touraine", "Roville-aux-Chênes", "Roville-devant-Bayon", +"Roy-Boissy", "Royaucourt-et-Chailvet", "Royaume-Uni", -"Roy-Boissy", -"Royère-de-Vassivière", "Roye-sur-Matz", +"Royère-de-Vassivière", +"Roz-Landrieux", +"Roz-sur-Couesnon", "Rozay-en-Brie", "Rozet-Saint-Albin", "Rozier-Côtes-d'Aurec", "Rozier-en-Donzy", +"Roziers-Saint-Georges", "Rozières-en-Beauce", "Rozières-sur-Crise", "Rozières-sur-Mouzon", -"Roziers-Saint-Georges", -"Roz-Landrieux", "Rozoy-Bellevalle", "Rozoy-le-Vieil", "Rozoy-sur-Serre", -"Roz-sur-Couesnon", -"RS-232", +"Roézé-sur-Sarthe", +"Roôcourt-la-Côte", "Ruan-sur-Egvonne", "Rubécourt-et-Lamécourt", "Rudeau-Ladosse", "Rudolfstetten-Friedlisberg", -"Rüdtligen-Alchenflüh", -"Rueil-la-Gadelière", "Rueil-Malmaison", +"Rueil-la-Gadelière", "Ruelle-sur-Touvre", "Rueyres-les-Prés", "Ruffey-le-Château", @@ -21584,14 +11488,10 @@ FR_BASE_EXCEPTIONS = [ "Ruffey-lès-Echirey", "Ruffey-lès-Échirey", "Ruffey-sur-Seille", -"rufino-sulfurique", -"rufino-sulfuriques", -"Ruillé-en-Champagne", "Ruillé-Froid-Fonds", +"Ruillé-en-Champagne", "Ruillé-le-Gravelais", "Ruillé-sur-Loir", -"ruine-babine", -"ruine-babines", "Rullac-Saint-Cirq", "Rumersheim-le-Haut", "Rumilly-en-Cambrésis", @@ -21604,26 +11504,42 @@ FR_BASE_EXCEPTIONS = [ "Rupt-sur-Othain", "Rupt-sur-Saône", "Rurange-lès-Thionville", -"russo-allemand", -"russo-allemande", -"russo-allemandes", -"russo-allemands", -"russo-américain", -"russo-japonaise", -"russo-polonaise", "Russy-Bémont", "Ruttersdorf-Lotschen", -"rü'üsá", +"Ruy-Montceau", "Ruynes-en-Margeride", -"R.-V.", +"Râlé-Poussé", +"Réaup-Lisse", +"Réchicourt-la-Petite", +"Réchicourt-le-Château", +"Récourt-le-Creux", +"Réez-Fosse-Martin", +"Régis-Borgien", +"Régis-Borgienne", +"Régis-Borgiennes", +"Régis-Borgiens", +"Régnié-Durette", +"Rémalard-en-Perche", +"Rémering-lès-Hargarten", +"Rémering-lès-Puttelange", +"Rémondans-Vaivre", +"Rémy-Montais", +"Rémy-Montaise", +"Rémy-Montaises", +"Réville-aux-Bois", +"Rödersheim-Gronau", +"Rötsweiler-Nockenthal", +"Rüdtligen-Alchenflüh", "S-6-verbénol", -"Saâcy-sur-Marne", +"S-chanf", +"S-métolachlore", +"S.-E.", +"S.-W.", "Saalburg-Ebersdorf", "Saaldorf-Surheim", "Saale-Holzland", "Saale-Orla", "Saalfeld-Rudolstadt", -"Saâne-Saint-Just", "Saar-Mark", "Saas-Almagell", "Saas-Balen", @@ -21631,132 +11547,3627 @@ FR_BASE_EXCEPTIONS = [ "Saas-Grund", "Sabadel-Latronquière", "Sabadel-Lauzès", -"sa'ban", -"Sablé-sur-Sarthe", "Sablons-sur-Huisne", -"sabre-peuple", -"saccharo-glycose", +"Sablé-sur-Sarthe", "Saceda-Trasierra", "Sacierges-Saint-Martin", -"sac-jacking", "Saconin-et-Breuil", -"sac-poubelle", -"sacré-coeur", -"sacré-cœur", "Sacré-Cœur", "Sacré-Cœurin", "Sacré-Cœurois", -"sacro-iliaques", -"sacro-lombaire", -"sacro-saint", -"sacro-sainte", -"sacro-saintement", -"sacro-saintes", -"sacro-saints", -"sacro-vertébral", -"sacs-poubelle", -"sacs-poubelles", "Sacy-le-Grand", "Sacy-le-Petit", -"sado-maso", -"sado-masochisme", -"sado-masochiste", -"sado-masochistes", -"safari-parc", -"safari-parcs", -"sage-femme", -"sage-homme", -"sages-femmes", "Sagnes-et-Goudoulet", "Saguenay-Jeannois", "Saguenay-Lac-Saint-Jean", -"sahélo-saharien", -"sahélo-saharienne", -"sahélo-sahariennes", -"sahélo-sahariens", -"saigne-nez", -"Saillat-sur-Vienne", "Sail-les-Bains", +"Sail-sous-Couzan", +"Saillat-sur-Vienne", "Sailly-Achâtel", -"Sailly-au-Bois", -"Sailly-en-Ostrevent", "Sailly-Flibeaucourt", "Sailly-Labourse", "Sailly-Laurette", +"Sailly-Saillisel", +"Sailly-au-Bois", +"Sailly-en-Ostrevent", "Sailly-le-Sec", "Sailly-lez-Cambrai", "Sailly-lez-Lannoy", -"Sailly-Saillisel", "Sailly-sur-la-Lys", -"Sail-sous-Couzan", "Sain-Bel", -"sain-belois", "Sain-Belois", -"sain-beloise", "Sain-Beloise", -"sain-beloises", "Sain-Beloises", -"sain-bois", "Saincaize-Meauce", -"sain-foin", "Sainghin-en-Mélantois", "Sainghin-en-Weppes", +"Sains-Morainvillers", +"Sains-Richaumont", "Sains-du-Nord", "Sains-en-Amiénois", "Sains-en-Gohelle", "Sains-lès-Fressin", "Sains-lès-Marquion", "Sains-lès-Pernes", -"Sains-Morainvillers", -"Sains-Richaumont", +"Saint Antoine l'Abbaye", +"Saint Aulaye-Puymangou", +"Saint Geniez d'Olt et d'Aubrac", +"Saint Martin de l'If", +"Saint-Abit", +"Saint-Abraham", +"Saint-Acheul", +"Saint-Adjutory", +"Saint-Adrien", +"Saint-Affrique", +"Saint-Affrique-les-Montagnes", +"Saint-Agathon", +"Saint-Agil", +"Saint-Agnan", +"Saint-Agnan-de-Cernières", +"Saint-Agnan-en-Vercors", +"Saint-Agnan-sur-Sarthe", +"Saint-Agnant", +"Saint-Agnant-de-Versillat", +"Saint-Agnant-près-Crocq", +"Saint-Agne", +"Saint-Agnet", +"Saint-Agnin-sur-Bion", +"Saint-Agoulin", +"Saint-Agrève", +"Saint-Aignan", +"Saint-Aignan-Grandlieu", +"Saint-Aignan-de-Couptrain", +"Saint-Aignan-de-Cramesnil", +"Saint-Aignan-des-Gués", +"Saint-Aignan-des-Noyers", +"Saint-Aignan-le-Jaillard", +"Saint-Aignan-sur-Roë", +"Saint-Aignan-sur-Ry", +"Saint-Aigny", +"Saint-Aigulin", +"Saint-Ail", +"Saint-Albain", +"Saint-Alban", +"Saint-Alban-Auriolles", +"Saint-Alban-Leysse", +"Saint-Alban-d'Ay", +"Saint-Alban-d'Hurtières", +"Saint-Alban-de-Montbel", +"Saint-Alban-de-Roche", +"Saint-Alban-des-Villards", +"Saint-Alban-du-Rhône", +"Saint-Alban-en-Montagne", +"Saint-Alban-les-Eaux", +"Saint-Alban-sur-Limagnole", +"Saint-Albin-de-Vaulserre", +"Saint-Alexandre", +"Saint-Algis", +"Saint-Allouestre", +"Saint-Alpinien", +"Saint-Alyre-d'Arlanc", +"Saint-Alyre-ès-Montagne", +"Saint-Amadou", +"Saint-Amancet", +"Saint-Amand", +"Saint-Amand-Jartoudeix", +"Saint-Amand-Longpré", +"Saint-Amand-Magnazeix", +"Saint-Amand-Montrond", +"Saint-Amand-de-Coly", +"Saint-Amand-de-Vergt", +"Saint-Amand-en-Puisaye", +"Saint-Amand-le-Petit", +"Saint-Amand-les-Eaux", +"Saint-Amand-sur-Fion", +"Saint-Amand-sur-Ornain", +"Saint-Amand-sur-Sèvre", +"Saint-Amandin", +"Saint-Amans", +"Saint-Amans-Soult", +"Saint-Amans-Valtoret", +"Saint-Amans-de-Pellagal", +"Saint-Amans-des-Cots", +"Saint-Amans-du-Pech", +"Saint-Amant-Roche-Savine", +"Saint-Amant-Tallende", +"Saint-Amant-de-Boixe", +"Saint-Amant-de-Bonnieure", +"Saint-Amant-de-Montmoreau", +"Saint-Amant-de-Nouère", +"Saint-Amarin", +"Saint-Ambreuil", +"Saint-Ambroix", +"Saint-Amour", +"Saint-Amour-Bellevue", +"Saint-Amé", +"Saint-Andelain", +"Saint-Andeux", +"Saint-Andiol", +"Saint-Androny", +"Saint-André", +"Saint-André-Capcèze", +"Saint-André-Farivillers", +"Saint-André-Goule-d'Oie", +"Saint-André-Lachamp", +"Saint-André-d'Allas", +"Saint-André-d'Apchon", +"Saint-André-d'Embrun", +"Saint-André-d'Huiriat", +"Saint-André-d'Hébertot", +"Saint-André-d'Olérargues", +"Saint-André-de-Bohon", +"Saint-André-de-Boëge", +"Saint-André-de-Briouze", +"Saint-André-de-Buèges", +"Saint-André-de-Bâgé", +"Saint-André-de-Chalencon", +"Saint-André-de-Corcy", +"Saint-André-de-Cruzières", +"Saint-André-de-Cubzac", +"Saint-André-de-Double", +"Saint-André-de-Lancize", +"Saint-André-de-Lidon", +"Saint-André-de-Majencoules", +"Saint-André-de-Messei", +"Saint-André-de-Najac", +"Saint-André-de-Roquelongue", +"Saint-André-de-Roquepertuis", +"Saint-André-de-Rosans", +"Saint-André-de-Sangonis", +"Saint-André-de-Seignanx", +"Saint-André-de-Valborgne", +"Saint-André-de-Vézines", +"Saint-André-de-l'Eure", +"Saint-André-de-l'Épine", +"Saint-André-de-la-Roche", +"Saint-André-des-Eaux", +"Saint-André-du-Bois", +"Saint-André-en-Barrois", +"Saint-André-en-Bresse", +"Saint-André-en-Morvan", +"Saint-André-en-Royans", +"Saint-André-en-Terre-Plaine", +"Saint-André-en-Vivarais", +"Saint-André-et-Appelles", +"Saint-André-la-Côte", +"Saint-André-le-Bouchoux", +"Saint-André-le-Coq", +"Saint-André-le-Désert", +"Saint-André-le-Gaz", +"Saint-André-le-Puy", +"Saint-André-les-Alpes", +"Saint-André-les-Vergers", +"Saint-André-lez-Lille", +"Saint-André-sur-Cailly", +"Saint-André-sur-Orne", +"Saint-André-sur-Sèvre", +"Saint-André-sur-Vieux-Jonc", +"Saint-Andéol", +"Saint-Andéol-de-Berg", +"Saint-Andéol-de-Fourchades", +"Saint-Andéol-de-Vals", +"Saint-Andéol-le-Château", +"Saint-Ange-et-Torçay", +"Saint-Ange-le-Viel", +"Saint-Angeau", +"Saint-Angel", +"Saint-Anthot", +"Saint-Anthème", +"Saint-Antoine", +"Saint-Antoine-Cumond", +"Saint-Antoine-d'Auberoche", +"Saint-Antoine-de-Breuilh", +"Saint-Antoine-de-Ficalba", +"Saint-Antoine-du-Queyret", +"Saint-Antoine-du-Rocher", +"Saint-Antoine-la-Forêt", +"Saint-Antoine-sur-l'Isle", +"Saint-Antonin", +"Saint-Antonin-Noble-Val", +"Saint-Antonin-de-Lacalm", +"Saint-Antonin-de-Sommaire", +"Saint-Antonin-du-Var", +"Saint-Antonin-sur-Bayon", +"Saint-Aoustrille", +"Saint-Août", +"Saint-Apollinaire", +"Saint-Apollinaire-de-Rias", +"Saint-Appolinaire", +"Saint-Appolinard", +"Saint-Aquilin", +"Saint-Aquilin-de-Corbion", +"Saint-Aquilin-de-Pacy", +"Saint-Araille", +"Saint-Arailles", +"Saint-Arcons-d'Allier", +"Saint-Arcons-de-Barges", +"Saint-Arey", +"Saint-Armel", +"Saint-Armou", +"Saint-Arnac", +"Saint-Arnoult", +"Saint-Arnoult-des-Bois", +"Saint-Arnoult-en-Yvelines", +"Saint-Arroman", +"Saint-Arroumex", +"Saint-Astier", +"Saint-Auban", +"Saint-Auban-d'Oze", +"Saint-Auban-sur-l'Ouvèze", +"Saint-Aubert", +"Saint-Aubin", +"Saint-Aubin-Celloville", +"Saint-Aubin-Fosse-Louvain", +"Saint-Aubin-Montenoy", +"Saint-Aubin-Rivière", +"Saint-Aubin-Routot", +"Saint-Aubin-d'Appenai", +"Saint-Aubin-d'Arquenay", +"Saint-Aubin-d'Aubigné", +"Saint-Aubin-d'Écrosville", +"Saint-Aubin-de-Blaye", +"Saint-Aubin-de-Bonneval", +"Saint-Aubin-de-Branne", +"Saint-Aubin-de-Cadelech", +"Saint-Aubin-de-Courteraie", +"Saint-Aubin-de-Crétot", +"Saint-Aubin-de-Lanquais", +"Saint-Aubin-de-Locquenay", +"Saint-Aubin-de-Médoc", +"Saint-Aubin-de-Nabirat", +"Saint-Aubin-de-Scellon", +"Saint-Aubin-de-Terregatte", +"Saint-Aubin-des-Bois", +"Saint-Aubin-des-Chaumes", +"Saint-Aubin-des-Châteaux", +"Saint-Aubin-des-Coudrais", +"Saint-Aubin-des-Landes", +"Saint-Aubin-des-Ormeaux", +"Saint-Aubin-des-Préaux", +"Saint-Aubin-du-Cormier", +"Saint-Aubin-du-Désert", +"Saint-Aubin-du-Pavail", +"Saint-Aubin-du-Perron", +"Saint-Aubin-du-Plain", +"Saint-Aubin-du-Thenney", +"Saint-Aubin-en-Bray", +"Saint-Aubin-en-Charollais", +"Saint-Aubin-la-Plaine", +"Saint-Aubin-le-Cauf", +"Saint-Aubin-le-Cloud", +"Saint-Aubin-le-Dépeint", +"Saint-Aubin-le-Monial", +"Saint-Aubin-le-Vertueux", +"Saint-Aubin-les-Forges", +"Saint-Aubin-lès-Elbeuf", +"Saint-Aubin-sous-Erquery", +"Saint-Aubin-sur-Aire", +"Saint-Aubin-sur-Gaillon", +"Saint-Aubin-sur-Loire", +"Saint-Aubin-sur-Mer", +"Saint-Aubin-sur-Quillebeuf", +"Saint-Aubin-sur-Scie", +"Saint-Aubin-sur-Yonne", +"Saint-Aubin-Épinay", +"Saint-Augustin", +"Saint-Augustin-des-Bois", +"Saint-Aulaire", +"Saint-Aulais-la-Chapelle", +"Saint-Aunix-Lengros", +"Saint-Aunès", +"Saint-Aupre", +"Saint-Austremoine", +"Saint-Auvent", +"Saint-Avaugourd-des-Landes", +"Saint-Aventin", +"Saint-Avertin", +"Saint-Avit", +"Saint-Avit-Frandat", +"Saint-Avit-Rivière", +"Saint-Avit-Saint-Nazaire", +"Saint-Avit-Sénieur", +"Saint-Avit-de-Soulège", +"Saint-Avit-de-Tardes", +"Saint-Avit-de-Vialard", +"Saint-Avit-le-Pauvre", +"Saint-Avit-les-Guespières", +"Saint-Avold", +"Saint-Avre", +"Saint-Avé", +"Saint-Ay", +"Saint-Aybert", +"Saint-Babel", +"Saint-Baldoph", +"Saint-Bandry", +"Saint-Baraing", +"Saint-Barbant", +"Saint-Bard", +"Saint-Bardoux", +"Saint-Barnabé", +"Saint-Barthélemy", +"Saint-Barthélemy-Grozon", +"Saint-Barthélemy-Lestra", +"Saint-Barthélemy-d'Agenais", +"Saint-Barthélemy-d'Anjou", +"Saint-Barthélemy-de-Bellegarde", +"Saint-Barthélemy-de-Bussière", +"Saint-Barthélemy-de-Séchilienne", +"Saint-Barthélemy-de-Vals", +"Saint-Barthélemy-le-Meil", +"Saint-Barthélemy-le-Plain", +"Saint-Basile", +"Saint-Baslemont", +"Saint-Baudel", +"Saint-Baudelle", +"Saint-Baudille-de-la-Tour", +"Saint-Baudille-et-Pipet", +"Saint-Bauld", +"Saint-Baussant", +"Saint-Bauzeil", +"Saint-Bauzile", +"Saint-Bauzille-de-Montmel", +"Saint-Bauzille-de-Putois", +"Saint-Bauzille-de-la-Sylve", +"Saint-Bauzély", +"Saint-Bazile", +"Saint-Bazile-de-Meyssac", +"Saint-Bazile-de-la-Roche", +"Saint-Beaulize", +"Saint-Beauzeil", +"Saint-Beauzile", +"Saint-Beauzire", +"Saint-Beauzély", +"Saint-Benin", +"Saint-Benin-d'Azy", +"Saint-Benin-des-Bois", +"Saint-Benoist-sur-Mer", +"Saint-Benoist-sur-Vanne", +"Saint-Benoit-en-Diois", +"Saint-Benoît", +"Saint-Benoît-d'Hébertot", +"Saint-Benoît-de-Carmaux", +"Saint-Benoît-des-Ombres", +"Saint-Benoît-des-Ondes", +"Saint-Benoît-du-Sault", +"Saint-Benoît-la-Chipotte", +"Saint-Benoît-la-Forêt", +"Saint-Benoît-sur-Loire", +"Saint-Benoît-sur-Seine", +"Saint-Berain-sous-Sanvignes", +"Saint-Bernard", +"Saint-Berthevin", +"Saint-Berthevin-la-Tannière", +"Saint-Bertrand-de-Comminges", +"Saint-Biez-en-Belin", +"Saint-Bihy", +"Saint-Blaise", +"Saint-Blaise-du-Buis", +"Saint-Blaise-la-Roche", +"Saint-Blancard", +"Saint-Blimont", +"Saint-Blin", +"Saint-Bohaire", +"Saint-Boil", +"Saint-Boingt", +"Saint-Bomer", +"Saint-Bon", +"Saint-Bon-Tarentaise", +"Saint-Bonnet", +"Saint-Bonnet-Avalouze", +"Saint-Bonnet-Briance", +"Saint-Bonnet-Elvert", +"Saint-Bonnet-Tronçais", +"Saint-Bonnet-de-Bellac", +"Saint-Bonnet-de-Chavagne", +"Saint-Bonnet-de-Chirac", +"Saint-Bonnet-de-Condat", +"Saint-Bonnet-de-Cray", +"Saint-Bonnet-de-Four", +"Saint-Bonnet-de-Joux", +"Saint-Bonnet-de-Montauroux", +"Saint-Bonnet-de-Mure", +"Saint-Bonnet-de-Rochefort", +"Saint-Bonnet-de-Salendrinque", +"Saint-Bonnet-de-Salers", +"Saint-Bonnet-de-Valclérieux", +"Saint-Bonnet-de-Vieille-Vigne", +"Saint-Bonnet-des-Bruyères", +"Saint-Bonnet-des-Quarts", +"Saint-Bonnet-du-Gard", +"Saint-Bonnet-en-Bresse", +"Saint-Bonnet-en-Champsaur", +"Saint-Bonnet-l'Enfantier", +"Saint-Bonnet-la-Rivière", +"Saint-Bonnet-le-Bourg", +"Saint-Bonnet-le-Chastel", +"Saint-Bonnet-le-Château", +"Saint-Bonnet-le-Courreau", +"Saint-Bonnet-le-Froid", +"Saint-Bonnet-le-Troncy", +"Saint-Bonnet-les-Oules", +"Saint-Bonnet-les-Tours-de-Merle", +"Saint-Bonnet-lès-Allier", +"Saint-Bonnet-près-Bort", +"Saint-Bonnet-près-Orcival", +"Saint-Bonnet-près-Riom", +"Saint-Bonnet-sur-Gironde", +"Saint-Bonnot", +"Saint-Bouize", +"Saint-Boès", +"Saint-Brancher", +"Saint-Branchs", +"Saint-Brandan", +"Saint-Bresson", +"Saint-Bressou", +"Saint-Brevin-les-Pins", +"Saint-Briac-sur-Mer", +"Saint-Brice", +"Saint-Brice-Courcelles", +"Saint-Brice-de-Landelles", +"Saint-Brice-en-Coglès", +"Saint-Brice-sous-Forêt", +"Saint-Brice-sous-Rânes", +"Saint-Brice-sur-Vienne", +"Saint-Brieuc", +"Saint-Brieuc-de-Mauron", +"Saint-Brieuc-des-Iffs", +"Saint-Bris-des-Bois", +"Saint-Bris-le-Vineux", +"Saint-Brisson", +"Saint-Brisson-sur-Loire", +"Saint-Broing", +"Saint-Broing-les-Moines", +"Saint-Broingt-le-Bois", +"Saint-Broingt-les-Fosses", +"Saint-Broladre", +"Saint-Brès", +"Saint-Bueil", +"Saint-Béat", +"Saint-Bénigne", +"Saint-Bénézet", +"Saint-Bérain", +"Saint-Bérain-sur-Dheune", +"Saint-Béron", +"Saint-Bômer-les-Forges", +"Saint-Calais", +"Saint-Calais-du-Désert", +"Saint-Calez-en-Saosnois", +"Saint-Cannat", +"Saint-Caprais", +"Saint-Caprais-de-Blaye", +"Saint-Caprais-de-Bordeaux", +"Saint-Caprais-de-Lerm", +"Saint-Capraise-d'Eymet", +"Saint-Capraise-de-Lalinde", +"Saint-Caradec", +"Saint-Caradec-Trégomel", +"Saint-Carné", +"Saint-Carreuc", +"Saint-Cassien", +"Saint-Cassin", +"Saint-Cast-le-Guildo", +"Saint-Castin", +"Saint-Cergues", +"Saint-Cernin", +"Saint-Cernin-de-Labarde", +"Saint-Cernin-de-Larche", +"Saint-Cernin-de-l'Herm", +"Saint-Chabrais", +"Saint-Chaffrey", +"Saint-Chamant", +"Saint-Chamarand", +"Saint-Chamas", +"Saint-Chamassy", +"Saint-Chamond", +"Saint-Champ", +"Saint-Chaptes", +"Saint-Charles-la-Forêt", +"Saint-Chartier", +"Saint-Chef", +"Saint-Chels", +"Saint-Chinian", +"Saint-Christ-Briost", +"Saint-Christaud", +"Saint-Christo-en-Jarez", +"Saint-Christol", +"Saint-Christol-de-Rodières", +"Saint-Christol-lès-Alès", +"Saint-Christoly-Médoc", +"Saint-Christoly-de-Blaye", +"Saint-Christophe", +"Saint-Christophe-Dodinicourt", +"Saint-Christophe-Vallon", +"Saint-Christophe-d'Allier", +"Saint-Christophe-de-Chaulieu", +"Saint-Christophe-de-Double", +"Saint-Christophe-de-Valains", +"Saint-Christophe-des-Bardes", +"Saint-Christophe-des-Bois", +"Saint-Christophe-du-Bois", +"Saint-Christophe-du-Foc", +"Saint-Christophe-du-Jambet", +"Saint-Christophe-du-Ligneron", +"Saint-Christophe-du-Luat", +"Saint-Christophe-en-Bazelle", +"Saint-Christophe-en-Boucherie", +"Saint-Christophe-en-Bresse", +"Saint-Christophe-en-Brionnais", +"Saint-Christophe-en-Champagne", +"Saint-Christophe-en-Oisans", +"Saint-Christophe-et-le-Laris", +"Saint-Christophe-le-Chaudry", +"Saint-Christophe-sur-Avre", +"Saint-Christophe-sur-Condé", +"Saint-Christophe-sur-Dolaison", +"Saint-Christophe-sur-Guiers", +"Saint-Christophe-sur-Roc", +"Saint-Christophe-sur-le-Nais", +"Saint-Christophe-à-Berry", +"Saint-Chély-d'Apcher", +"Saint-Chély-d'Aubrac", +"Saint-Chéron", +"Saint-Cibard", +"Saint-Cierge-la-Serre", +"Saint-Cierge-sous-le-Cheylard", +"Saint-Ciergues", +"Saint-Ciers-Champagne", +"Saint-Ciers-d'Abzac", +"Saint-Ciers-de-Canesse", +"Saint-Ciers-du-Taillon", +"Saint-Ciers-sur-Bonnieure", +"Saint-Ciers-sur-Gironde", +"Saint-Cirgue", +"Saint-Cirgues", +"Saint-Cirgues-de-Jordanne", +"Saint-Cirgues-de-Malbert", +"Saint-Cirgues-de-Prades", +"Saint-Cirgues-en-Montagne", +"Saint-Cirgues-la-Loutre", +"Saint-Cirgues-sur-Couze", +"Saint-Cirice", +"Saint-Cirq", +"Saint-Cirq-Lapopie", +"Saint-Cirq-Madelon", +"Saint-Cirq-Souillaguet", +"Saint-Civran", +"Saint-Clair", +"Saint-Clair-d'Arcey", +"Saint-Clair-de-Halouze", +"Saint-Clair-de-la-Tour", +"Saint-Clair-du-Rhône", +"Saint-Clair-sur-Epte", +"Saint-Clair-sur-Galaure", +"Saint-Clair-sur-l'Elle", +"Saint-Clair-sur-les-Monts", +"Saint-Clar", +"Saint-Clar-de-Rivière", +"Saint-Claud", +"Saint-Claude", +"Saint-Claude-de-Diray", +"Saint-Clet", +"Saint-Cloud", +"Saint-Cloud-en-Dunois", +"Saint-Clément", +"Saint-Clément-Rancoudray", +"Saint-Clément-de-Rivière", +"Saint-Clément-de-Régnat", +"Saint-Clément-de-Valorgue", +"Saint-Clément-de-Vers", +"Saint-Clément-de-la-Place", +"Saint-Clément-des-Baleines", +"Saint-Clément-des-Levées", +"Saint-Clément-les-Places", +"Saint-Clément-sur-Durance", +"Saint-Clément-sur-Guye", +"Saint-Clément-sur-Valsonne", +"Saint-Clément-à-Arnes", +"Saint-Colomb-de-Lauzun", +"Saint-Colomban", +"Saint-Colomban-des-Villards", +"Saint-Congard", +"Saint-Connan", +"Saint-Connec", +"Saint-Constant-Fournoulès", +"Saint-Contest", +"Saint-Corneille", +"Saint-Cosme", +"Saint-Cosme-en-Vairais", +"Saint-Couat-d'Aude", +"Saint-Couat-du-Razès", +"Saint-Coulitz", +"Saint-Coulomb", +"Saint-Coutant", +"Saint-Coutant-le-Grand", +"Saint-Crespin", +"Saint-Cricq", +"Saint-Cricq-Chalosse", +"Saint-Cricq-Villeneuve", +"Saint-Cricq-du-Gave", +"Saint-Créac", +"Saint-Crépin", +"Saint-Crépin-Ibouvillers", +"Saint-Crépin-aux-Bois", +"Saint-Crépin-d'Auberoche", +"Saint-Crépin-de-Richemont", +"Saint-Crépin-et-Carlucet", +"Saint-Cybardeaux", +"Saint-Cybranet", +"Saint-Cyprien", +"Saint-Cyr", +"Saint-Cyr-Montmalin", +"Saint-Cyr-au-Mont-d'Or", +"Saint-Cyr-de-Favières", +"Saint-Cyr-de-Salerne", +"Saint-Cyr-de-Valorges", +"Saint-Cyr-des-Gâts", +"Saint-Cyr-du-Bailleul", +"Saint-Cyr-du-Doret", +"Saint-Cyr-du-Gault", +"Saint-Cyr-en-Arthies", +"Saint-Cyr-en-Bourg", +"Saint-Cyr-en-Pail", +"Saint-Cyr-en-Talmondais", +"Saint-Cyr-en-Val", +"Saint-Cyr-l'École", +"Saint-Cyr-la-Campagne", +"Saint-Cyr-la-Lande", +"Saint-Cyr-la-Rivière", +"Saint-Cyr-la-Roche", +"Saint-Cyr-la-Rosière", +"Saint-Cyr-le-Chatoux", +"Saint-Cyr-le-Gravelais", +"Saint-Cyr-les-Champagnes", +"Saint-Cyr-les-Colons", +"Saint-Cyr-les-Vignes", +"Saint-Cyr-sous-Dourdan", +"Saint-Cyr-sur-Loire", +"Saint-Cyr-sur-Menthon", +"Saint-Cyr-sur-Mer", +"Saint-Cyr-sur-Morin", +"Saint-Cyr-sur-le-Rhône", +"Saint-Cyran-du-Jambot", +"Saint-Célerin", +"Saint-Céneri-le-Gérei", +"Saint-Céneré", +"Saint-Céols", +"Saint-Céré", +"Saint-Césaire", +"Saint-Césaire-de-Gauzignan", +"Saint-Cézaire-sur-Siagne", +"Saint-Cézert", +"Saint-Côme", +"Saint-Côme-d'Olt", +"Saint-Côme-de-Fresné", +"Saint-Côme-et-Maruéjols", +"Saint-Dalmas-le-Selvage", +"Saint-Daunès", +"Saint-Denis", +"Saint-Denis-Catus", +"Saint-Denis-Combarnazat", +"Saint-Denis-d'Aclon", +"Saint-Denis-d'Anjou", +"Saint-Denis-d'Augerons", +"Saint-Denis-d'Authou", +"Saint-Denis-d'Oléron", +"Saint-Denis-d'Orques", +"Saint-Denis-de-Cabanne", +"Saint-Denis-de-Gastines", +"Saint-Denis-de-Jouhet", +"Saint-Denis-de-Mailloc", +"Saint-Denis-de-Méré", +"Saint-Denis-de-Palin", +"Saint-Denis-de-Pile", +"Saint-Denis-de-Vaux", +"Saint-Denis-de-l'Hôtel", +"Saint-Denis-des-Coudrais", +"Saint-Denis-des-Monts", +"Saint-Denis-des-Murs", +"Saint-Denis-des-Puits", +"Saint-Denis-du-Maine", +"Saint-Denis-du-Payré", +"Saint-Denis-en-Bugey", +"Saint-Denis-en-Margeride", +"Saint-Denis-en-Val", +"Saint-Denis-la-Chevasse", +"Saint-Denis-le-Ferment", +"Saint-Denis-le-Gast", +"Saint-Denis-le-Thiboult", +"Saint-Denis-le-Vêtu", +"Saint-Denis-les-Ponts", +"Saint-Denis-lès-Bourg", +"Saint-Denis-lès-Martel", +"Saint-Denis-lès-Rebais", +"Saint-Denis-lès-Sens", +"Saint-Denis-sur-Coise", +"Saint-Denis-sur-Huisne", +"Saint-Denis-sur-Loire", +"Saint-Denis-sur-Sarthon", +"Saint-Denis-sur-Scie", +"Saint-Deniscourt", +"Saint-Denoual", +"Saint-Denœux", +"Saint-Derrien", +"Saint-Didier", +"Saint-Didier-au-Mont-d'Or", +"Saint-Didier-d'Allier", +"Saint-Didier-d'Aussiat", +"Saint-Didier-de-Bizonnes", +"Saint-Didier-de-Formans", +"Saint-Didier-de-la-Tour", +"Saint-Didier-des-Bois", +"Saint-Didier-en-Bresse", +"Saint-Didier-en-Brionnais", +"Saint-Didier-en-Donjon", +"Saint-Didier-en-Velay", +"Saint-Didier-la-Forêt", +"Saint-Didier-sous-Aubenas", +"Saint-Didier-sous-Riverie", +"Saint-Didier-sous-Écouves", +"Saint-Didier-sur-Arroux", +"Saint-Didier-sur-Beaujeu", +"Saint-Didier-sur-Chalaronne", +"Saint-Didier-sur-Doulon", +"Saint-Didier-sur-Rochefort", +"Saint-Dier-d'Auvergne", +"Saint-Dionisy", +"Saint-Divy", +"Saint-Dizant-du-Bois", +"Saint-Dizant-du-Gua", +"Saint-Dizier", +"Saint-Dizier-Leyrenne", +"Saint-Dizier-en-Diois", +"Saint-Dizier-l'Évêque", +"Saint-Dizier-la-Tour", +"Saint-Dizier-les-Domaines", +"Saint-Dié-des-Vosges", +"Saint-Diéry", +"Saint-Dolay", +"Saint-Domet", +"Saint-Domineuc", +"Saint-Donan", +"Saint-Donat", +"Saint-Donat-sur-l'Herbasse", +"Saint-Dos", +"Saint-Doulchard", +"Saint-Drézéry", +"Saint-Dyé-sur-Loire", +"Saint-Désert", +"Saint-Désir", +"Saint-Désirat", +"Saint-Désiré", +"Saint-Dézéry", +"Saint-Edmond", +"Saint-Ellier-du-Maine", +"Saint-Ellier-les-Bois", +"Saint-Eloy", +"Saint-Ennemond", +"Saint-Epvre", +"Saint-Erblon", +"Saint-Erme-Outre-et-Ramecourt", +"Saint-Escobille", +"Saint-Esprit", +"Saint-Esteben", +"Saint-Estèphe", +"Saint-Estève", +"Saint-Estève-Janson", +"Saint-Eugène", +"Saint-Eulien", +"Saint-Euphraise-et-Clairizet", +"Saint-Euphrône", +"Saint-Eustache", +"Saint-Eustache-la-Forêt", +"Saint-Eusèbe", +"Saint-Eusèbe-en-Champsaur", +"Saint-Eutrope", +"Saint-Eutrope-de-Born", +"Saint-Evroult-Notre-Dame-du-Bois", +"Saint-Evroult-de-Montfort", +"Saint-Exupéry", +"Saint-Exupéry-les-Roches", +"Saint-Fargeau", +"Saint-Fargeau-Ponthierry", +"Saint-Fargeol", +"Saint-Faust", +"Saint-Fergeux", +"Saint-Ferjeux", +"Saint-Ferme", +"Saint-Ferriol", +"Saint-Ferréol", +"Saint-Ferréol-Trente-Pas", +"Saint-Ferréol-d'Auroure", +"Saint-Ferréol-de-Comminges", +"Saint-Ferréol-des-Côtes", +"Saint-Fiacre", +"Saint-Fiacre-sur-Maine", +"Saint-Fiel", +"Saint-Firmin", +"Saint-Firmin-des-Bois", +"Saint-Firmin-des-Prés", +"Saint-Firmin-sur-Loire", +"Saint-Flavy", +"Saint-Florent", +"Saint-Florent-sur-Auzonnet", +"Saint-Florent-sur-Cher", +"Saint-Florentin", +"Saint-Floret", +"Saint-Floris", +"Saint-Flour", +"Saint-Flour-de-Mercoire", +"Saint-Flovier", +"Saint-Floxel", +"Saint-Folquin", +"Saint-Fons", +"Saint-Forgeot", +"Saint-Forget", +"Saint-Forgeux", +"Saint-Forgeux-Lespinasse", +"Saint-Fort", +"Saint-Fort-sur-Gironde", +"Saint-Fort-sur-le-Né", +"Saint-Fortunat-sur-Eyrieux", +"Saint-Fraigne", +"Saint-Fraimbault", +"Saint-Fraimbault-de-Prières", +"Saint-Frajou", +"Saint-Franc", +"Saint-Franchy", +"Saint-François", +"Saint-François-Lacroix", +"Saint-François-Longchamp", +"Saint-François-de-Sales", +"Saint-Frichoux", +"Saint-Frion", +"Saint-Fromond", +"Saint-Front", +"Saint-Front-d'Alemps", +"Saint-Front-de-Pradoux", +"Saint-Front-la-Rivière", +"Saint-Front-sur-Lémance", +"Saint-Front-sur-Nizonne", +"Saint-Froult", +"Saint-Frégant", +"Saint-Fréjoux", +"Saint-Frézal-d'Albuges", +"Saint-Fulgent", +"Saint-Fulgent-des-Ormes", +"Saint-Fuscien", +"Saint-Félicien", +"Saint-Féliu-d'Amont", +"Saint-Féliu-d'Avall", +"Saint-Félix", +"Saint-Félix-Lauragais", +"Saint-Félix-de-Bourdeilles", +"Saint-Félix-de-Foncaude", +"Saint-Félix-de-Lodez", +"Saint-Félix-de-Lunel", +"Saint-Félix-de-Pallières", +"Saint-Félix-de-Reillac-et-Mortemart", +"Saint-Félix-de-Rieutord", +"Saint-Félix-de-Sorgues", +"Saint-Félix-de-Tournegat", +"Saint-Félix-de-Villadeix", +"Saint-Félix-de-l'Héras", +"Saint-Gabriel-Brécy", +"Saint-Gal", +"Saint-Gal-sur-Sioule", +"Saint-Galmier", +"Saint-Gand", +"Saint-Ganton", +"Saint-Gatien-des-Bois", +"Saint-Gaudens", +"Saint-Gaudent", +"Saint-Gaudéric", +"Saint-Gaultier", +"Saint-Gauzens", +"Saint-Gein", +"Saint-Gelais", +"Saint-Gelven", +"Saint-Gence", +"Saint-Genest", +"Saint-Genest-Lachamp", +"Saint-Genest-Lerpt", +"Saint-Genest-Malifaux", +"Saint-Genest-d'Ambière", +"Saint-Genest-de-Beauzon", +"Saint-Genest-de-Contest", +"Saint-Genest-sur-Roselle", +"Saint-Geneys-près-Saint-Paulien", +"Saint-Gengoulph", +"Saint-Gengoux-de-Scissé", +"Saint-Gengoux-le-National", +"Saint-Geniez", +"Saint-Geniez-ô-Merle", +"Saint-Genis-Laval", +"Saint-Genis-Pouilly", +"Saint-Genis-d'Hiersac", +"Saint-Genis-de-Saintonge", +"Saint-Genis-du-Bois", +"Saint-Genis-l'Argentière", +"Saint-Genis-les-Ollières", +"Saint-Genis-sur-Menthon", +"Saint-Genix-sur-Guiers", +"Saint-Geniès", +"Saint-Geniès-Bellevue", +"Saint-Geniès-de-Comolas", +"Saint-Geniès-de-Fontedit", +"Saint-Geniès-de-Malgoirès", +"Saint-Geniès-de-Varensal", +"Saint-Geniès-des-Mourgues", +"Saint-Genou", +"Saint-Genouph", +"Saint-Genès-Champanelle", +"Saint-Genès-Champespe", +"Saint-Genès-de-Blaye", +"Saint-Genès-de-Castillon", +"Saint-Genès-de-Fronsac", +"Saint-Genès-de-Lombaud", +"Saint-Genès-du-Retz", +"Saint-Genès-la-Tourette", +"Saint-Geoire-en-Valdaine", +"Saint-Geoirs", +"Saint-Georges", +"Saint-Georges-Antignac", +"Saint-Georges-Armont", +"Saint-Georges-Blancaneix", +"Saint-Georges-Buttavent", +"Saint-Georges-Haute-Ville", +"Saint-Georges-Lagricol", +"Saint-Georges-Montcocq", +"Saint-Georges-Motel", +"Saint-Georges-Nigremont", +"Saint-Georges-d'Annebecq", +"Saint-Georges-d'Aurac", +"Saint-Georges-d'Elle", +"Saint-Georges-d'Espéranche", +"Saint-Georges-d'Hurtières", +"Saint-Georges-d'Oléron", +"Saint-Georges-d'Orques", +"Saint-Georges-de-Baroille", +"Saint-Georges-de-Chesné", +"Saint-Georges-de-Commiers", +"Saint-Georges-de-Didonne", +"Saint-Georges-de-Gréhaigne", +"Saint-Georges-de-Livoye", +"Saint-Georges-de-Longuepierre", +"Saint-Georges-de-Luzençon", +"Saint-Georges-de-Lévéjac", +"Saint-Georges-de-Mons", +"Saint-Georges-de-Montaigu", +"Saint-Georges-de-Montclard", +"Saint-Georges-de-Noisné", +"Saint-Georges-de-Pointindoux", +"Saint-Georges-de-Poisieux", +"Saint-Georges-de-Reintembault", +"Saint-Georges-de-Reneins", +"Saint-Georges-de-Rex", +"Saint-Georges-de-Rouelley", +"Saint-Georges-de-la-Couée", +"Saint-Georges-de-la-Rivière", +"Saint-Georges-des-Agoûts", +"Saint-Georges-des-Coteaux", +"Saint-Georges-des-Groseillers", +"Saint-Georges-du-Bois", +"Saint-Georges-du-Mesnil", +"Saint-Georges-du-Rosay", +"Saint-Georges-du-Vièvre", +"Saint-Georges-en-Auge", +"Saint-Georges-en-Couzan", +"Saint-Georges-la-Pouge", +"Saint-Georges-le-Fléchard", +"Saint-Georges-le-Gaultier", +"Saint-Georges-les-Bains", +"Saint-Georges-les-Landes", +"Saint-Georges-lès-Baillargeaux", +"Saint-Georges-sur-Allier", +"Saint-Georges-sur-Arnon", +"Saint-Georges-sur-Baulche", +"Saint-Georges-sur-Cher", +"Saint-Georges-sur-Erve", +"Saint-Georges-sur-Eure", +"Saint-Georges-sur-Fontaine", +"Saint-Georges-sur-Layon", +"Saint-Georges-sur-Loire", +"Saint-Georges-sur-Moulon", +"Saint-Georges-sur-Renon", +"Saint-Georges-sur-l'Aa", +"Saint-Georges-sur-la-Prée", +"Saint-Geours-d'Auribat", +"Saint-Geours-de-Maremne", +"Saint-Germain", +"Saint-Germain-Beaupré", +"Saint-Germain-Chassenay", +"Saint-Germain-Langot", +"Saint-Germain-Laprade", +"Saint-Germain-Laval", +"Saint-Germain-Lavolps", +"Saint-Germain-Laxis", +"Saint-Germain-Lembron", +"Saint-Germain-Lespinasse", +"Saint-Germain-Nuelles", +"Saint-Germain-Village", +"Saint-Germain-au-Mont-d'Or", +"Saint-Germain-d'Anxure", +"Saint-Germain-d'Arcé", +"Saint-Germain-d'Aunay", +"Saint-Germain-d'Ectot", +"Saint-Germain-d'Elle", +"Saint-Germain-d'Esteuil", +"Saint-Germain-d'Étables", +"Saint-Germain-de-Belvès", +"Saint-Germain-de-Calberte", +"Saint-Germain-de-Clairefeuille", +"Saint-Germain-de-Coulamer", +"Saint-Germain-de-Fresney", +"Saint-Germain-de-Grave", +"Saint-Germain-de-Joux", +"Saint-Germain-de-Livet", +"Saint-Germain-de-Longue-Chaume", +"Saint-Germain-de-Lusignan", +"Saint-Germain-de-Marencennes", +"Saint-Germain-de-Martigny", +"Saint-Germain-de-Modéon", +"Saint-Germain-de-Montbron", +"Saint-Germain-de-Pasquier", +"Saint-Germain-de-Prinçay", +"Saint-Germain-de-Salles", +"Saint-Germain-de-Tournebut", +"Saint-Germain-de-Varreville", +"Saint-Germain-de-Vibrac", +"Saint-Germain-de-la-Coudre", +"Saint-Germain-de-la-Grange", +"Saint-Germain-de-la-Rivière", +"Saint-Germain-des-Angles", +"Saint-Germain-des-Bois", +"Saint-Germain-des-Champs", +"Saint-Germain-des-Essourts", +"Saint-Germain-des-Fossés", +"Saint-Germain-des-Grois", +"Saint-Germain-des-Prés", +"Saint-Germain-des-Vaux", +"Saint-Germain-du-Bel-Air", +"Saint-Germain-du-Bois", +"Saint-Germain-du-Corbéis", +"Saint-Germain-du-Pert", +"Saint-Germain-du-Pinel", +"Saint-Germain-du-Plain", +"Saint-Germain-du-Puch", +"Saint-Germain-du-Puy", +"Saint-Germain-du-Salembre", +"Saint-Germain-du-Seudre", +"Saint-Germain-du-Teil", +"Saint-Germain-en-Brionnais", +"Saint-Germain-en-Coglès", +"Saint-Germain-en-Laye", +"Saint-Germain-en-Montagne", +"Saint-Germain-et-Mons", +"Saint-Germain-l'Herm", +"Saint-Germain-la-Blanche-Herbe", +"Saint-Germain-la-Campagne", +"Saint-Germain-la-Montagne", +"Saint-Germain-la-Poterie", +"Saint-Germain-la-Ville", +"Saint-Germain-le-Châtelet", +"Saint-Germain-le-Fouilloux", +"Saint-Germain-le-Gaillard", +"Saint-Germain-le-Guillaume", +"Saint-Germain-le-Rocheux", +"Saint-Germain-le-Vasson", +"Saint-Germain-le-Vieux", +"Saint-Germain-les-Belles", +"Saint-Germain-les-Paroisses", +"Saint-Germain-les-Vergnes", +"Saint-Germain-lès-Arpajon", +"Saint-Germain-lès-Buxy", +"Saint-Germain-lès-Corbeil", +"Saint-Germain-lès-Senailly", +"Saint-Germain-près-Herment", +"Saint-Germain-sous-Cailly", +"Saint-Germain-sous-Doue", +"Saint-Germain-sur-Avre", +"Saint-Germain-sur-Ay", +"Saint-Germain-sur-Bresle", +"Saint-Germain-sur-Eaulne", +"Saint-Germain-sur-Ille", +"Saint-Germain-sur-Meuse", +"Saint-Germain-sur-Morin", +"Saint-Germain-sur-Renon", +"Saint-Germain-sur-Rhône", +"Saint-Germain-sur-Sarthe", +"Saint-Germain-sur-Sèves", +"Saint-Germain-sur-Vienne", +"Saint-Germain-sur-École", +"Saint-Germainmont", +"Saint-Germer-de-Fly", +"Saint-Germier", +"Saint-Germé", +"Saint-Gervais", +"Saint-Gervais-d'Auvergne", +"Saint-Gervais-de-Vic", +"Saint-Gervais-des-Sablons", +"Saint-Gervais-du-Perron", +"Saint-Gervais-en-Belin", +"Saint-Gervais-en-Vallière", +"Saint-Gervais-la-Forêt", +"Saint-Gervais-les-Bains", +"Saint-Gervais-les-Trois-Clochers", +"Saint-Gervais-sous-Meymont", +"Saint-Gervais-sur-Couches", +"Saint-Gervais-sur-Mare", +"Saint-Gervais-sur-Roubion", +"Saint-Gervasy", +"Saint-Gervazy", +"Saint-Geyrac", +"Saint-Gibrien", +"Saint-Gildas", +"Saint-Gildas-de-Rhuys", +"Saint-Gildas-des-Bois", +"Saint-Gilles", +"Saint-Gilles-Croix-de-Vie", +"Saint-Gilles-Pligeaux", +"Saint-Gilles-Vieux-Marché", +"Saint-Gilles-de-Crétot", +"Saint-Gilles-de-la-Neuville", +"Saint-Gilles-des-Marais", +"Saint-Gilles-les-Bois", +"Saint-Gilles-les-Forêts", +"Saint-Gineis-en-Coiron", +"Saint-Gingolph", +"Saint-Girons", +"Saint-Girons-d'Aiguevives", +"Saint-Girons-en-Béarn", +"Saint-Gladie-Arrive-Munein", +"Saint-Glen", +"Saint-Goazec", +"Saint-Gobain", +"Saint-Gobert", +"Saint-Goin", +"Saint-Gondon", +"Saint-Gondran", +"Saint-Gonlay", +"Saint-Gonnery", +"Saint-Gor", +"Saint-Gorgon", +"Saint-Gorgon-Main", +"Saint-Gourgon", +"Saint-Gourson", +"Saint-Goussaud", +"Saint-Gratien", +"Saint-Gratien-Savigny", +"Saint-Gravé", +"Saint-Griède", +"Saint-Groux", +"Saint-Grégoire", +"Saint-Grégoire-d'Ardennes", +"Saint-Grégoire-du-Vièvre", +"Saint-Guen", +"Saint-Guilhem-le-Désert", +"Saint-Guillaume", +"Saint-Guinoux", +"Saint-Guiraud", +"Saint-Guyomard", +"Saint-Gély-du-Fesc", +"Saint-Génard", +"Saint-Génis-des-Fontaines", +"Saint-Généroux", +"Saint-Gérand", +"Saint-Gérand-de-Vaux", +"Saint-Gérand-le-Puy", +"Saint-Géraud", +"Saint-Géraud-de-Corps", +"Saint-Géron", +"Saint-Gérons", +"Saint-Géry", +"Saint-Géréon", +"Saint-Haon", +"Saint-Haon-le-Châtel", +"Saint-Haon-le-Vieux", +"Saint-Hellier", +"Saint-Herblain", +"Saint-Hernin", +"Saint-Hervé", +"Saint-Hilaire", +"Saint-Hilaire-Bonneval", +"Saint-Hilaire-Cottes", +"Saint-Hilaire-Cusson-la-Valmitte", +"Saint-Hilaire-Foissac", +"Saint-Hilaire-Fontaine", +"Saint-Hilaire-Luc", +"Saint-Hilaire-Petitville", +"Saint-Hilaire-Peyroux", +"Saint-Hilaire-Saint-Mesmin", +"Saint-Hilaire-Taurieux", +"Saint-Hilaire-au-Temple", +"Saint-Hilaire-d'Estissac", +"Saint-Hilaire-d'Ozilhan", +"Saint-Hilaire-de-Beauvoir", +"Saint-Hilaire-de-Brens", +"Saint-Hilaire-de-Brethmas", +"Saint-Hilaire-de-Briouze", +"Saint-Hilaire-de-Chaléons", +"Saint-Hilaire-de-Clisson", +"Saint-Hilaire-de-Court", +"Saint-Hilaire-de-Gondilly", +"Saint-Hilaire-de-Lavit", +"Saint-Hilaire-de-Loulay", +"Saint-Hilaire-de-Lusignan", +"Saint-Hilaire-de-Riez", +"Saint-Hilaire-de-Villefranche", +"Saint-Hilaire-de-Voust", +"Saint-Hilaire-de-la-Côte", +"Saint-Hilaire-de-la-Noaille", +"Saint-Hilaire-des-Landes", +"Saint-Hilaire-des-Loges", +"Saint-Hilaire-du-Bois", +"Saint-Hilaire-du-Harcouët", +"Saint-Hilaire-du-Maine", +"Saint-Hilaire-du-Rosier", +"Saint-Hilaire-en-Lignières", +"Saint-Hilaire-en-Morvan", +"Saint-Hilaire-en-Woëvre", +"Saint-Hilaire-la-Croix", +"Saint-Hilaire-la-Forêt", +"Saint-Hilaire-la-Gravelle", +"Saint-Hilaire-la-Gérard", +"Saint-Hilaire-la-Palud", +"Saint-Hilaire-la-Plaine", +"Saint-Hilaire-la-Treille", +"Saint-Hilaire-le-Château", +"Saint-Hilaire-le-Châtel", +"Saint-Hilaire-le-Grand", +"Saint-Hilaire-le-Petit", +"Saint-Hilaire-le-Vouhis", +"Saint-Hilaire-les-Andrésis", +"Saint-Hilaire-les-Courbes", +"Saint-Hilaire-les-Monges", +"Saint-Hilaire-les-Places", +"Saint-Hilaire-lez-Cambrai", +"Saint-Hilaire-sous-Charlieu", +"Saint-Hilaire-sous-Romilly", +"Saint-Hilaire-sur-Benaize", +"Saint-Hilaire-sur-Erre", +"Saint-Hilaire-sur-Helpe", +"Saint-Hilaire-sur-Puiseaux", +"Saint-Hilaire-sur-Risle", +"Saint-Hilaire-sur-Yerre", +"Saint-Hilarion", +"Saint-Hilliers", +"Saint-Hippolyte", +"Saint-Hippolyte-de-Caton", +"Saint-Hippolyte-de-Montaigu", +"Saint-Hippolyte-du-Fort", +"Saint-Hippolyte-le-Graveyron", +"Saint-Honoré", +"Saint-Honoré-les-Bains", +"Saint-Hostien", +"Saint-Hubert", +"Saint-Huruge", +"Saint-Hymer", +"Saint-Hymetière", +"Saint-Héand", +"Saint-Hélen", +"Saint-Hélier", +"Saint-Hérent", +"Saint-Igeaux", +"Saint-Igest", +"Saint-Ignan", +"Saint-Ignat", +"Saint-Igny-de-Roche", +"Saint-Igny-de-Vers", +"Saint-Illide", +"Saint-Illiers-la-Ville", +"Saint-Illiers-le-Bois", +"Saint-Ilpize", +"Saint-Imoges", +"Saint-Inglevert", +"Saint-Ismier", +"Saint-Izaire", +"Saint-Jacques", +"Saint-Jacques-d'Aliermont", +"Saint-Jacques-d'Ambur", +"Saint-Jacques-d'Atticieux", +"Saint-Jacques-de-Néhou", +"Saint-Jacques-de-Thouars", +"Saint-Jacques-de-la-Lande", +"Saint-Jacques-des-Arrêts", +"Saint-Jacques-des-Blats", +"Saint-Jacques-des-Guérets", +"Saint-Jacques-en-Valgodemard", +"Saint-Jacques-sur-Darnétal", +"Saint-Jacut-de-la-Mer", +"Saint-Jacut-les-Pins", +"Saint-Jal", +"Saint-James", +"Saint-Jammes", +"Saint-Jans-Cappel", +"Saint-Jean", +"Saint-Jean-Bonnefonds", +"Saint-Jean-Brévelay", +"Saint-Jean-Cap-Ferrat", +"Saint-Jean-Chambre", +"Saint-Jean-Delnous", +"Saint-Jean-Froidmentel", +"Saint-Jean-Kerdaniel", +"Saint-Jean-Kourtzerode", +"Saint-Jean-Lachalm", +"Saint-Jean-Lagineste", +"Saint-Jean-Lasseille", +"Saint-Jean-Lespinasse", +"Saint-Jean-Lherm", +"Saint-Jean-Ligoure", +"Saint-Jean-Mirabel", +"Saint-Jean-Pied-de-Port", +"Saint-Jean-Pierre-Fixte", +"Saint-Jean-Pla-de-Corts", +"Saint-Jean-Poudge", +"Saint-Jean-Poutge", +"Saint-Jean-Rohrbach", +"Saint-Jean-Roure", +"Saint-Jean-Saint-Germain", +"Saint-Jean-Saint-Gervais", +"Saint-Jean-Saint-Maurice-sur-Loire", +"Saint-Jean-Saint-Nicolas", +"Saint-Jean-Saverne", +"Saint-Jean-Soleymieux", +"Saint-Jean-Trolimon", +"Saint-Jean-aux-Amognes", +"Saint-Jean-aux-Bois", +"Saint-Jean-d'Aigues-Vives", +"Saint-Jean-d'Alcapiès", +"Saint-Jean-d'Angle", +"Saint-Jean-d'Angély", +"Saint-Jean-d'Ardières", +"Saint-Jean-d'Arves", +"Saint-Jean-d'Arvey", +"Saint-Jean-d'Assé", +"Saint-Jean-d'Ataux", +"Saint-Jean-d'Aubrigoux", +"Saint-Jean-d'Aulps", +"Saint-Jean-d'Avelanne", +"Saint-Jean-d'Elle", +"Saint-Jean-d'Estissac", +"Saint-Jean-d'Eyraud", +"Saint-Jean-d'Heurs", +"Saint-Jean-d'Hérans", +"Saint-Jean-d'Illac", +"Saint-Jean-d'Ormont", +"Saint-Jean-d'Étreux", +"Saint-Jean-de-Barrou", +"Saint-Jean-de-Bassel", +"Saint-Jean-de-Beauregard", +"Saint-Jean-de-Belleville", +"Saint-Jean-de-Beugné", +"Saint-Jean-de-Blaignac", +"Saint-Jean-de-Boiseau", +"Saint-Jean-de-Bonneval", +"Saint-Jean-de-Bournay", +"Saint-Jean-de-Braye", +"Saint-Jean-de-Buèges", +"Saint-Jean-de-Bœuf", +"Saint-Jean-de-Ceyrargues", +"Saint-Jean-de-Chevelu", +"Saint-Jean-de-Cornies", +"Saint-Jean-de-Couz", +"Saint-Jean-de-Crieulon", +"Saint-Jean-de-Cuculles", +"Saint-Jean-de-Côle", +"Saint-Jean-de-Daye", +"Saint-Jean-de-Duras", +"Saint-Jean-de-Folleville", +"Saint-Jean-de-Fos", +"Saint-Jean-de-Gonville", +"Saint-Jean-de-Laur", +"Saint-Jean-de-Lier", +"Saint-Jean-de-Linières", +"Saint-Jean-de-Liversay", +"Saint-Jean-de-Livet", +"Saint-Jean-de-Losne", +"Saint-Jean-de-Luz", +"Saint-Jean-de-Marcel", +"Saint-Jean-de-Marsacq", +"Saint-Jean-de-Maruéjols-et-Avéjan", +"Saint-Jean-de-Maurienne", +"Saint-Jean-de-Minervois", +"Saint-Jean-de-Moirans", +"Saint-Jean-de-Monts", +"Saint-Jean-de-Muzols", +"Saint-Jean-de-Nay", +"Saint-Jean-de-Niost", +"Saint-Jean-de-Paracol", +"Saint-Jean-de-Rebervilliers", +"Saint-Jean-de-Rives", +"Saint-Jean-de-Sauves", +"Saint-Jean-de-Savigny", +"Saint-Jean-de-Serres", +"Saint-Jean-de-Sixt", +"Saint-Jean-de-Soudain", +"Saint-Jean-de-Tholome", +"Saint-Jean-de-Thouars", +"Saint-Jean-de-Thurac", +"Saint-Jean-de-Thurigneux", +"Saint-Jean-de-Touslas", +"Saint-Jean-de-Trézy", +"Saint-Jean-de-Vals", +"Saint-Jean-de-Valériscle", +"Saint-Jean-de-Vaulx", +"Saint-Jean-de-Vaux", +"Saint-Jean-de-Verges", +"Saint-Jean-de-Védas", +"Saint-Jean-de-la-Blaquière", +"Saint-Jean-de-la-Croix", +"Saint-Jean-de-la-Haize", +"Saint-Jean-de-la-Léqueraye", +"Saint-Jean-de-la-Motte", +"Saint-Jean-de-la-Neuville", +"Saint-Jean-de-la-Porte", +"Saint-Jean-de-la-Rivière", +"Saint-Jean-de-la-Ruelle", +"Saint-Jean-des-Champs", +"Saint-Jean-des-Essartiers", +"Saint-Jean-des-Mauvrets", +"Saint-Jean-des-Ollières", +"Saint-Jean-des-Vignes", +"Saint-Jean-des-Échelles", +"Saint-Jean-devant-Possesse", +"Saint-Jean-du-Bois", +"Saint-Jean-du-Bouzet", +"Saint-Jean-du-Bruel", +"Saint-Jean-du-Cardonnay", +"Saint-Jean-du-Castillonnais", +"Saint-Jean-du-Corail-des-Bois", +"Saint-Jean-du-Doigt", +"Saint-Jean-du-Falga", +"Saint-Jean-du-Gard", +"Saint-Jean-du-Pin", +"Saint-Jean-du-Thenney", +"Saint-Jean-en-Royans", +"Saint-Jean-en-Val", +"Saint-Jean-et-Saint-Paul", +"Saint-Jean-la-Bussière", +"Saint-Jean-la-Fouillouse", +"Saint-Jean-la-Poterie", +"Saint-Jean-la-Vêtre", +"Saint-Jean-le-Blanc", +"Saint-Jean-le-Centenier", +"Saint-Jean-le-Comtal", +"Saint-Jean-le-Thomas", +"Saint-Jean-le-Vieux", +"Saint-Jean-les-Deux-Jumeaux", +"Saint-Jean-lès-Buzy", +"Saint-Jean-lès-Longuyon", +"Saint-Jean-sur-Couesnon", +"Saint-Jean-sur-Erve", +"Saint-Jean-sur-Mayenne", +"Saint-Jean-sur-Moivre", +"Saint-Jean-sur-Reyssouze", +"Saint-Jean-sur-Tourbe", +"Saint-Jean-sur-Veyle", +"Saint-Jean-sur-Vilaine", +"Saint-Jeannet", +"Saint-Jeanvrin", +"Saint-Jeoire", +"Saint-Jeoire-Prieuré", +"Saint-Jeure-d'Andaure", +"Saint-Jeure-d'Ay", +"Saint-Jeures", +"Saint-Joachim", +"Saint-Jodard", +"Saint-Joire", +"Saint-Jorioz", +"Saint-Jory", +"Saint-Jory-de-Chalais", +"Saint-Jory-las-Bloux", +"Saint-Joseph", +"Saint-Joseph-de-Rivière", +"Saint-Joseph-des-Bancs", +"Saint-Josse", +"Saint-Jouan-de-l'Isle", +"Saint-Jouan-des-Guérets", +"Saint-Jouin", +"Saint-Jouin-Bruneval", +"Saint-Jouin-de-Blavou", +"Saint-Jouin-de-Marnes", +"Saint-Jouin-de-Milly", +"Saint-Jouvent", +"Saint-Juan", +"Saint-Judoce", +"Saint-Juire-Champgillon", +"Saint-Julia", +"Saint-Julia-de-Bec", +"Saint-Julien", +"Saint-Julien-Beychevelle", +"Saint-Julien-Boutières", +"Saint-Julien-Chapteuil", +"Saint-Julien-Gaulène", +"Saint-Julien-Labrousse", +"Saint-Julien-Maumont", +"Saint-Julien-Molhesabate", +"Saint-Julien-Molin-Molette", +"Saint-Julien-Mont-Denis", +"Saint-Julien-Puy-Lavèze", +"Saint-Julien-Vocance", +"Saint-Julien-aux-Bois", +"Saint-Julien-d'Ance", +"Saint-Julien-d'Armagnac", +"Saint-Julien-d'Asse", +"Saint-Julien-d'Eymet", +"Saint-Julien-d'Oddes", +"Saint-Julien-de-Briola", +"Saint-Julien-de-Cassagnas", +"Saint-Julien-de-Chédon", +"Saint-Julien-de-Civry", +"Saint-Julien-de-Concelles", +"Saint-Julien-de-Coppel", +"Saint-Julien-de-Crempse", +"Saint-Julien-de-Gras-Capou", +"Saint-Julien-de-Jonzy", +"Saint-Julien-de-Lampon", +"Saint-Julien-de-Peyrolas", +"Saint-Julien-de-Raz", +"Saint-Julien-de-Toursac", +"Saint-Julien-de-Vouvantes", +"Saint-Julien-de-l'Escap", +"Saint-Julien-de-l'Herms", +"Saint-Julien-de-la-Liègue", +"Saint-Julien-de-la-Nef", +"Saint-Julien-des-Chazes", +"Saint-Julien-des-Landes", +"Saint-Julien-des-Points", +"Saint-Julien-du-Gua", +"Saint-Julien-du-Pinet", +"Saint-Julien-du-Puy", +"Saint-Julien-du-Sault", +"Saint-Julien-du-Serre", +"Saint-Julien-du-Terroux", +"Saint-Julien-du-Tournel", +"Saint-Julien-du-Verdon", +"Saint-Julien-en-Beauchêne", +"Saint-Julien-en-Born", +"Saint-Julien-en-Champsaur", +"Saint-Julien-en-Genevois", +"Saint-Julien-en-Quint", +"Saint-Julien-en-Saint-Alban", +"Saint-Julien-en-Vercors", +"Saint-Julien-l'Ars", +"Saint-Julien-la-Geneste", +"Saint-Julien-la-Genête", +"Saint-Julien-la-Vêtre", +"Saint-Julien-le-Châtel", +"Saint-Julien-le-Faucon", +"Saint-Julien-le-Petit", +"Saint-Julien-le-Pèlerin", +"Saint-Julien-le-Roux", +"Saint-Julien-le-Vendômois", +"Saint-Julien-les-Rosiers", +"Saint-Julien-les-Villas", +"Saint-Julien-lès-Gorze", +"Saint-Julien-lès-Metz", +"Saint-Julien-lès-Montbéliard", +"Saint-Julien-lès-Russey", +"Saint-Julien-près-Bort", +"Saint-Julien-sous-les-Côtes", +"Saint-Julien-sur-Bibost", +"Saint-Julien-sur-Calonne", +"Saint-Julien-sur-Cher", +"Saint-Julien-sur-Dheune", +"Saint-Julien-sur-Garonne", +"Saint-Julien-sur-Reyssouze", +"Saint-Julien-sur-Sarthe", +"Saint-Julien-sur-Veyle", +"Saint-Junien", +"Saint-Junien-la-Bregère", +"Saint-Junien-les-Combes", +"Saint-Jure", +"Saint-Jurs", +"Saint-Just", +"Saint-Just-Chaleyssin", +"Saint-Just-Ibarre", +"Saint-Just-Luzac", +"Saint-Just-Malmont", +"Saint-Just-Saint-Rambert", +"Saint-Just-Sauvage", +"Saint-Just-d'Ardèche", +"Saint-Just-d'Avray", +"Saint-Just-de-Claix", +"Saint-Just-en-Bas", +"Saint-Just-en-Brie", +"Saint-Just-en-Chaussée", +"Saint-Just-en-Chevalet", +"Saint-Just-et-Vacquières", +"Saint-Just-et-le-Bézu", +"Saint-Just-la-Pendue", +"Saint-Just-le-Martel", +"Saint-Just-près-Brioude", +"Saint-Just-sur-Dive", +"Saint-Just-sur-Viaur", +"Saint-Justin", +"Saint-Juvat", +"Saint-Juvin", +"Saint-Juéry", +"Saint-Lactencin", +"Saint-Lager", +"Saint-Lager-Bressac", +"Saint-Lamain", +"Saint-Lambert", +"Saint-Lambert-et-Mont-de-Jeux", +"Saint-Lambert-la-Potherie", +"Saint-Lambert-sur-Dive", +"Saint-Langis-lès-Mortagne", +"Saint-Lanne", +"Saint-Laon", +"Saint-Lary", +"Saint-Lary-Boujean", +"Saint-Lary-Soulan", +"Saint-Lattier", +"Saint-Launeuc", +"Saint-Laure", +"Saint-Laurent", +"Saint-Laurent-Blangy", +"Saint-Laurent-Bretagne", +"Saint-Laurent-Chabreuges", +"Saint-Laurent-Lolmie", +"Saint-Laurent-Médoc", +"Saint-Laurent-Nouan", +"Saint-Laurent-Rochefort", +"Saint-Laurent-d'Agny", +"Saint-Laurent-d'Aigouze", +"Saint-Laurent-d'Andenay", +"Saint-Laurent-d'Arce", +"Saint-Laurent-d'Oingt", +"Saint-Laurent-d'Olt", +"Saint-Laurent-d'Onay", +"Saint-Laurent-de-Belzagot", +"Saint-Laurent-de-Brèvedent", +"Saint-Laurent-de-Carnols", +"Saint-Laurent-de-Cerdans", +"Saint-Laurent-de-Chamousset", +"Saint-Laurent-de-Cognac", +"Saint-Laurent-de-Condel", +"Saint-Laurent-de-Cuves", +"Saint-Laurent-de-Céris", +"Saint-Laurent-de-Gosse", +"Saint-Laurent-de-Jourdes", +"Saint-Laurent-de-Lin", +"Saint-Laurent-de-Lévézou", +"Saint-Laurent-de-Mure", +"Saint-Laurent-de-Muret", +"Saint-Laurent-de-Neste", +"Saint-Laurent-de-Terregatte", +"Saint-Laurent-de-Veyrès", +"Saint-Laurent-de-la-Barrière", +"Saint-Laurent-de-la-Cabrerisse", +"Saint-Laurent-de-la-Prée", +"Saint-Laurent-de-la-Salanque", +"Saint-Laurent-de-la-Salle", +"Saint-Laurent-des-Arbres", +"Saint-Laurent-des-Bois", +"Saint-Laurent-des-Combes", +"Saint-Laurent-des-Hommes", +"Saint-Laurent-des-Mortiers", +"Saint-Laurent-des-Vignes", +"Saint-Laurent-du-Bois", +"Saint-Laurent-du-Cros", +"Saint-Laurent-du-Maroni", +"Saint-Laurent-du-Mont", +"Saint-Laurent-du-Pape", +"Saint-Laurent-du-Plan", +"Saint-Laurent-du-Pont", +"Saint-Laurent-du-Tencement", +"Saint-Laurent-du-Var", +"Saint-Laurent-du-Verdon", +"Saint-Laurent-en-Beaumont", +"Saint-Laurent-en-Brionnais", +"Saint-Laurent-en-Caux", +"Saint-Laurent-en-Grandvaux", +"Saint-Laurent-en-Gâtines", +"Saint-Laurent-en-Royans", +"Saint-Laurent-l'Abbaye", +"Saint-Laurent-la-Conche", +"Saint-Laurent-la-Gâtine", +"Saint-Laurent-la-Vallée", +"Saint-Laurent-la-Vernède", +"Saint-Laurent-le-Minier", +"Saint-Laurent-les-Bains", +"Saint-Laurent-les-Tours", +"Saint-Laurent-les-Églises", +"Saint-Laurent-sous-Coiron", +"Saint-Laurent-sur-Gorre", +"Saint-Laurent-sur-Mer", +"Saint-Laurent-sur-Othain", +"Saint-Laurent-sur-Oust", +"Saint-Laurent-sur-Saône", +"Saint-Laurent-sur-Sèvre", +"Saint-Laurs", +"Saint-Leu", +"Saint-Leu-d'Esserent", +"Saint-Leu-la-Forêt", +"Saint-Lieux-Lafenasse", +"Saint-Lieux-lès-Lavaur", +"Saint-Lin", +"Saint-Lions", +"Saint-Lizier", +"Saint-Lizier-du-Planté", +"Saint-Lon-les-Mines", +"Saint-Longis", +"Saint-Lormel", +"Saint-Lothain", +"Saint-Loube", +"Saint-Loubert", +"Saint-Loubouer", +"Saint-Loubès", +"Saint-Louet-sur-Seulles", +"Saint-Louet-sur-Vire", +"Saint-Louis", +"Saint-Louis-de-Montferrand", +"Saint-Louis-en-l'Isle", +"Saint-Louis-et-Parahou", +"Saint-Louis-lès-Bitche", +"Saint-Loup", +"Saint-Loup-Cammas", +"Saint-Loup-Géanges", +"Saint-Loup-Hors", +"Saint-Loup-Lamairé", +"Saint-Loup-Nantouard", +"Saint-Loup-Terrier", +"Saint-Loup-d'Ordon", +"Saint-Loup-de-Buffigny", +"Saint-Loup-de-Fribois", +"Saint-Loup-de-Gonois", +"Saint-Loup-de-Naud", +"Saint-Loup-de-Varennes", +"Saint-Loup-des-Chaumes", +"Saint-Loup-des-Vignes", +"Saint-Loup-du-Dorat", +"Saint-Loup-du-Gast", +"Saint-Loup-en-Champagne", +"Saint-Loup-en-Comminges", +"Saint-Loup-sur-Aujon", +"Saint-Loup-sur-Semouse", +"Saint-Lubin-de-Cravant", +"Saint-Lubin-de-la-Haye", +"Saint-Lubin-des-Joncherets", +"Saint-Lubin-en-Vergonnois", +"Saint-Luc", +"Saint-Lucien", +"Saint-Lumier-en-Champagne", +"Saint-Lumier-la-Populeuse", +"Saint-Lumine-de-Clisson", +"Saint-Lumine-de-Coutais", +"Saint-Lunaire", +"Saint-Luperce", +"Saint-Lupicin", +"Saint-Lupien", +"Saint-Lyphard", +"Saint-Lys", +"Saint-Lyé", +"Saint-Lyé-la-Forêt", +"Saint-Léger", +"Saint-Léger-Bridereix", +"Saint-Léger-Dubosq", +"Saint-Léger-Magnazeix", +"Saint-Léger-Triey", +"Saint-Léger-Vauban", +"Saint-Léger-aux-Bois", +"Saint-Léger-de-Balson", +"Saint-Léger-de-Fougeret", +"Saint-Léger-de-Montbrillais", +"Saint-Léger-de-Montbrun", +"Saint-Léger-de-Peyre", +"Saint-Léger-de-Rôtes", +"Saint-Léger-de-la-Martinière", +"Saint-Léger-des-Aubées", +"Saint-Léger-des-Bois", +"Saint-Léger-des-Prés", +"Saint-Léger-des-Vignes", +"Saint-Léger-du-Bois", +"Saint-Léger-du-Bourg-Denis", +"Saint-Léger-du-Gennetey", +"Saint-Léger-du-Malzieu", +"Saint-Léger-du-Ventoux", +"Saint-Léger-en-Bray", +"Saint-Léger-en-Yvelines", +"Saint-Léger-la-Montagne", +"Saint-Léger-le-Guérétois", +"Saint-Léger-le-Petit", +"Saint-Léger-les-Mélèzes", +"Saint-Léger-les-Vignes", +"Saint-Léger-lès-Authie", +"Saint-Léger-lès-Domart", +"Saint-Léger-lès-Paray", +"Saint-Léger-près-Troyes", +"Saint-Léger-sous-Beuvray", +"Saint-Léger-sous-Brienne", +"Saint-Léger-sous-Cholet", +"Saint-Léger-sous-Margerie", +"Saint-Léger-sous-la-Bussière", +"Saint-Léger-sur-Bresle", +"Saint-Léger-sur-Dheune", +"Saint-Léger-sur-Roanne", +"Saint-Léger-sur-Sarthe", +"Saint-Léger-sur-Vouzance", +"Saint-Léomer", +"Saint-Léon", +"Saint-Léon-d'Issigeac", +"Saint-Léon-sur-Vézère", +"Saint-Léon-sur-l'Isle", +"Saint-Léonard", +"Saint-Léonard-de-Noblat", +"Saint-Léonard-des-Bois", +"Saint-Léonard-des-Parcs", +"Saint-Léonard-en-Beauce", +"Saint-Léons", +"Saint-Léopardin-d'Augy", +"Saint-Léry", +"Saint-Lézer", +"Saint-Lô", +"Saint-Lô-d'Ourville", +"Saint-M'Hervon", +"Saint-M'Hervé", +"Saint-Macaire", +"Saint-Macaire-du-Bois", +"Saint-Maclou", +"Saint-Maclou-de-Folleville", +"Saint-Maclou-la-Brière", +"Saint-Macoux", +"Saint-Maden", +"Saint-Magne", +"Saint-Magne-de-Castillon", +"Saint-Maigner", +"Saint-Maigrin", +"Saint-Maime", +"Saint-Maime-de-Péreyrol", +"Saint-Maixant", +"Saint-Maixent", +"Saint-Maixent-de-Beugné", +"Saint-Maixent-l'École", +"Saint-Maixent-sur-Vie", +"Saint-Maixme-Hauterive", +"Saint-Malo", +"Saint-Malo-de-Beignon", +"Saint-Malo-de-Guersac", +"Saint-Malo-de-Phily", +"Saint-Malo-de-la-Lande", +"Saint-Malo-des-Trois-Fontaines", +"Saint-Malo-en-Donziois", +"Saint-Malon-sur-Mel", +"Saint-Malô-du-Bois", +"Saint-Mamert", +"Saint-Mamert-du-Gard", +"Saint-Mamet", +"Saint-Mamet-la-Salvetat", +"Saint-Mammès", +"Saint-Mandrier-sur-Mer", +"Saint-Mandé", +"Saint-Mandé-sur-Brédoire", +"Saint-Manvieu-Bocage", +"Saint-Manvieu-Norrey", +"Saint-Marc-Jaumegarde", +"Saint-Marc-du-Cor", +"Saint-Marc-la-Lande", +"Saint-Marc-le-Blanc", +"Saint-Marc-sur-Couesnon", +"Saint-Marc-sur-Seine", +"Saint-Marc-à-Frongier", +"Saint-Marc-à-Loubaud", +"Saint-Marcan", +"Saint-Marceau", +"Saint-Marcel", +"Saint-Marcel-Bel-Accueil", +"Saint-Marcel-Campes", +"Saint-Marcel-Paulel", +"Saint-Marcel-d'Ardèche", +"Saint-Marcel-d'Urfé", +"Saint-Marcel-de-Careiret", +"Saint-Marcel-de-Félines", +"Saint-Marcel-du-Périgord", +"Saint-Marcel-en-Marcillat", +"Saint-Marcel-en-Murat", +"Saint-Marcel-l'Éclairé", +"Saint-Marcel-lès-Annonay", +"Saint-Marcel-lès-Sauzet", +"Saint-Marcel-lès-Valence", +"Saint-Marcel-sur-Aude", +"Saint-Marcelin-de-Cray", +"Saint-Marcellin", +"Saint-Marcellin-en-Forez", +"Saint-Marcellin-lès-Vaison", +"Saint-Marcet", +"Saint-Marcory", +"Saint-Marcouf", +"Saint-Mard", +"Saint-Mard-de-Réno", +"Saint-Mard-de-Vaux", +"Saint-Mard-lès-Rouffy", +"Saint-Mard-sur-Auve", +"Saint-Mard-sur-le-Mont", +"Saint-Mards", +"Saint-Mards-de-Blacarville", +"Saint-Mards-de-Fresne", +"Saint-Mards-en-Othe", +"Saint-Marien", +"Saint-Mariens", +"Saint-Mars-Vieux-Maisons", +"Saint-Mars-d'Outillé", +"Saint-Mars-d'Égrenne", +"Saint-Mars-de-Coutais", +"Saint-Mars-de-Locquenay", +"Saint-Mars-du-Désert", +"Saint-Mars-la-Brière", +"Saint-Mars-la-Jaille", +"Saint-Mars-la-Réorthe", +"Saint-Mars-sur-Colmont", +"Saint-Mars-sur-la-Futaie", +"Saint-Marsal", +"Saint-Martial", +"Saint-Martial-Entraygues", +"Saint-Martial-Viveyrol", +"Saint-Martial-d'Albarède", +"Saint-Martial-d'Artenset", +"Saint-Martial-de-Gimel", +"Saint-Martial-de-Mirambeau", +"Saint-Martial-de-Nabirat", +"Saint-Martial-de-Valette", +"Saint-Martial-de-Vitaterne", +"Saint-Martial-le-Mont", +"Saint-Martial-le-Vieux", +"Saint-Martial-sur-Isop", +"Saint-Martial-sur-Né", +"Saint-Martin", +"Saint-Martin-Belle-Roche", +"Saint-Martin-Bellevue", +"Saint-Martin-Boulogne", +"Saint-Martin-Cantalès", +"Saint-Martin-Choquel", +"Saint-Martin-Château", +"Saint-Martin-Curton", +"Saint-Martin-Gimois", +"Saint-Martin-Labouval", +"Saint-Martin-Lacaussade", +"Saint-Martin-Laguépie", +"Saint-Martin-Lalande", +"Saint-Martin-Lars-en-Sainte-Hermine", +"Saint-Martin-Lestra", +"Saint-Martin-Longueau", +"Saint-Martin-Lys", +"Saint-Martin-Osmonville", +"Saint-Martin-Petit", +"Saint-Martin-Rivière", +"Saint-Martin-Saint-Firmin", +"Saint-Martin-Sainte-Catherine", +"Saint-Martin-Sepert", +"Saint-Martin-Terressus", +"Saint-Martin-Valmeroux", +"Saint-Martin-Vésubie", +"Saint-Martin-au-Bosc", +"Saint-Martin-aux-Arbres", +"Saint-Martin-aux-Bois", +"Saint-Martin-aux-Buneaux", +"Saint-Martin-aux-Champs", +"Saint-Martin-aux-Chartrains", +"Saint-Martin-d'Abbat", +"Saint-Martin-d'Ablois", +"Saint-Martin-d'Août", +"Saint-Martin-d'Arberoue", +"Saint-Martin-d'Arc", +"Saint-Martin-d'Ardèche", +"Saint-Martin-d'Armagnac", +"Saint-Martin-d'Arrossa", +"Saint-Martin-d'Ary", +"Saint-Martin-d'Aubigny", +"Saint-Martin-d'Audouville", +"Saint-Martin-d'Auxigny", +"Saint-Martin-d'Auxy", +"Saint-Martin-d'Entraunes", +"Saint-Martin-d'Estréaux", +"Saint-Martin-d'Hardinghem", +"Saint-Martin-d'Heuille", +"Saint-Martin-d'Hères", +"Saint-Martin-d'Ollières", +"Saint-Martin-d'Oney", +"Saint-Martin-d'Ordon", +"Saint-Martin-d'Oydes", +"Saint-Martin-d'Uriage", +"Saint-Martin-d'Écublei", +"Saint-Martin-de-Bavel", +"Saint-Martin-de-Beauville", +"Saint-Martin-de-Bernegoue", +"Saint-Martin-de-Bienfaite-la-Cressonnière", +"Saint-Martin-de-Blagny", +"Saint-Martin-de-Bonfossé", +"Saint-Martin-de-Boscherville", +"Saint-Martin-de-Bossenay", +"Saint-Martin-de-Boubaux", +"Saint-Martin-de-Bréthencourt", +"Saint-Martin-de-Brômes", +"Saint-Martin-de-Caralp", +"Saint-Martin-de-Castillon", +"Saint-Martin-de-Cenilly", +"Saint-Martin-de-Clelles", +"Saint-Martin-de-Commune", +"Saint-Martin-de-Connée", +"Saint-Martin-de-Coux", +"Saint-Martin-de-Crau", +"Saint-Martin-de-Fenouillet", +"Saint-Martin-de-Fontenay", +"Saint-Martin-de-Fraigneau", +"Saint-Martin-de-Fressengeas", +"Saint-Martin-de-Fugères", +"Saint-Martin-de-Goyne", +"Saint-Martin-de-Gurson", +"Saint-Martin-de-Hinx", +"Saint-Martin-de-Juillers", +"Saint-Martin-de-Jussac", +"Saint-Martin-de-Lansuscle", +"Saint-Martin-de-Laye", +"Saint-Martin-de-Lenne", +"Saint-Martin-de-Lerm", +"Saint-Martin-de-Lixy", +"Saint-Martin-de-Londres", +"Saint-Martin-de-Mailloc", +"Saint-Martin-de-Mieux", +"Saint-Martin-de-Mâcon", +"Saint-Martin-de-Nigelles", +"Saint-Martin-de-Pallières", +"Saint-Martin-de-Queyrières", +"Saint-Martin-de-Ribérac", +"Saint-Martin-de-Ré", +"Saint-Martin-de-Saint-Maixent", +"Saint-Martin-de-Salencey", +"Saint-Martin-de-Sanzay", +"Saint-Martin-de-Seignanx", +"Saint-Martin-de-Sescas", +"Saint-Martin-de-Valamas", +"Saint-Martin-de-Valgalgues", +"Saint-Martin-de-Varreville", +"Saint-Martin-de-Vaulserre", +"Saint-Martin-de-Villereglan", +"Saint-Martin-de-Villeréal", +"Saint-Martin-de-l'Arçon", +"Saint-Martin-de-la-Brasque", +"Saint-Martin-de-la-Cluze", +"Saint-Martin-de-la-Lieue", +"Saint-Martin-de-la-Mer", +"Saint-Martin-de-la-Place", +"Saint-Martin-de-la-Porte", +"Saint-Martin-des-Bois", +"Saint-Martin-des-Champs", +"Saint-Martin-des-Combes", +"Saint-Martin-des-Entrées", +"Saint-Martin-des-Fontaines", +"Saint-Martin-des-Lais", +"Saint-Martin-des-Landes", +"Saint-Martin-des-Monts", +"Saint-Martin-des-Noyers", +"Saint-Martin-des-Olmes", +"Saint-Martin-des-Plains", +"Saint-Martin-des-Prés", +"Saint-Martin-des-Puits", +"Saint-Martin-des-Pézerits", +"Saint-Martin-des-Tilleuls", +"Saint-Martin-du-Bec", +"Saint-Martin-du-Bois", +"Saint-Martin-du-Boschet", +"Saint-Martin-du-Clocher", +"Saint-Martin-du-Fouilloux", +"Saint-Martin-du-Frêne", +"Saint-Martin-du-Lac", +"Saint-Martin-du-Limet", +"Saint-Martin-du-Manoir", +"Saint-Martin-du-Mont", +"Saint-Martin-du-Puy", +"Saint-Martin-du-Tartre", +"Saint-Martin-du-Tertre", +"Saint-Martin-du-Tilleul", +"Saint-Martin-du-Var", +"Saint-Martin-du-Vieux-Bellême", +"Saint-Martin-du-Vivier", +"Saint-Martin-en-Bière", +"Saint-Martin-en-Bresse", +"Saint-Martin-en-Gâtinois", +"Saint-Martin-en-Haut", +"Saint-Martin-en-Vercors", +"Saint-Martin-l'Aiguillon", +"Saint-Martin-l'Ars", +"Saint-Martin-l'Astier", +"Saint-Martin-l'Heureux", +"Saint-Martin-l'Hortier", +"Saint-Martin-la-Campagne", +"Saint-Martin-la-Garenne", +"Saint-Martin-la-Méanne", +"Saint-Martin-la-Patrouille", +"Saint-Martin-la-Plaine", +"Saint-Martin-la-Sauveté", +"Saint-Martin-le-Beau", +"Saint-Martin-le-Bouillant", +"Saint-Martin-le-Châtel", +"Saint-Martin-le-Colonel", +"Saint-Martin-le-Gaillard", +"Saint-Martin-le-Gréard", +"Saint-Martin-le-Mault", +"Saint-Martin-le-Nœud", +"Saint-Martin-le-Pin", +"Saint-Martin-le-Redon", +"Saint-Martin-le-Vieil", +"Saint-Martin-le-Vieux", +"Saint-Martin-le-Vinoux", +"Saint-Martin-les-Eaux", +"Saint-Martin-lez-Tatinghem", +"Saint-Martin-lès-Langres", +"Saint-Martin-lès-Melle", +"Saint-Martin-lès-Seyne", +"Saint-Martin-sous-Montaigu", +"Saint-Martin-sous-Vigouroux", +"Saint-Martin-sur-Armançon", +"Saint-Martin-sur-Cojeul", +"Saint-Martin-sur-Lavezon", +"Saint-Martin-sur-Nohain", +"Saint-Martin-sur-Ocre", +"Saint-Martin-sur-Oust", +"Saint-Martin-sur-la-Chambre", +"Saint-Martin-sur-le-Pré", +"Saint-Martin-sur-Écaillon", +"Saint-Martinien", +"Saint-Martory", +"Saint-Mary", +"Saint-Mary-le-Plain", +"Saint-Masmes", +"Saint-Mathieu", +"Saint-Mathieu-de-Tréviers", +"Saint-Mathurin", +"Saint-Matré", +"Saint-Maudan", +"Saint-Maudez", +"Saint-Maugan", +"Saint-Maulvis", +"Saint-Maur", +"Saint-Maur-des-Bois", +"Saint-Maur-des-Fossés", +"Saint-Maur-sur-le-Loir", +"Saint-Maurice", +"Saint-Maurice-Colombier", +"Saint-Maurice-Crillat", +"Saint-Maurice-Montcouronne", +"Saint-Maurice-Navacelles", +"Saint-Maurice-Saint-Germain", +"Saint-Maurice-Thizouaille", +"Saint-Maurice-aux-Forges", +"Saint-Maurice-aux-Riches-Hommes", +"Saint-Maurice-d'Ardèche", +"Saint-Maurice-d'Ibie", +"Saint-Maurice-d'Ételan", +"Saint-Maurice-de-Beynost", +"Saint-Maurice-de-Cazevieille", +"Saint-Maurice-de-Gourdans", +"Saint-Maurice-de-Lestapel", +"Saint-Maurice-de-Lignon", +"Saint-Maurice-de-Rotherens", +"Saint-Maurice-de-Rémens", +"Saint-Maurice-de-Satonnay", +"Saint-Maurice-des-Champs", +"Saint-Maurice-des-Lions", +"Saint-Maurice-des-Noues", +"Saint-Maurice-en-Chalencon", +"Saint-Maurice-en-Cotentin", +"Saint-Maurice-en-Gourgois", +"Saint-Maurice-en-Quercy", +"Saint-Maurice-en-Rivière", +"Saint-Maurice-en-Trièves", +"Saint-Maurice-en-Valgodemard", +"Saint-Maurice-l'Exil", +"Saint-Maurice-la-Clouère", +"Saint-Maurice-la-Souterraine", +"Saint-Maurice-le-Girard", +"Saint-Maurice-le-Vieil", +"Saint-Maurice-les-Brousses", +"Saint-Maurice-lès-Charencey", +"Saint-Maurice-lès-Châteauneuf", +"Saint-Maurice-lès-Couches", +"Saint-Maurice-près-Crocq", +"Saint-Maurice-près-Pionsat", +"Saint-Maurice-sous-les-Côtes", +"Saint-Maurice-sur-Adour", +"Saint-Maurice-sur-Aveyron", +"Saint-Maurice-sur-Dargoire", +"Saint-Maurice-sur-Eygues", +"Saint-Maurice-sur-Fessard", +"Saint-Maurice-sur-Mortagne", +"Saint-Maurice-sur-Moselle", +"Saint-Maurice-sur-Vingeanne", +"Saint-Maurin", +"Saint-Max", +"Saint-Maxent", +"Saint-Maximin", +"Saint-Maximin-la-Sainte-Baume", +"Saint-Maxire", +"Saint-May", +"Saint-Mayeux", +"Saint-Melaine-sur-Aubance", +"Saint-Memmie", +"Saint-Menge", +"Saint-Menges", +"Saint-Menoux", +"Saint-Merd-de-Lapleau", +"Saint-Merd-la-Breuille", +"Saint-Merd-les-Oussines", +"Saint-Meslin-du-Bosc", +"Saint-Mesmes", +"Saint-Mesmin", +"Saint-Mexant", +"Saint-Micaud", +"Saint-Michel", +"Saint-Michel-Chef-Chef", +"Saint-Michel-Escalus", +"Saint-Michel-Labadié", +"Saint-Michel-Loubéjou", +"Saint-Michel-Tubœuf", +"Saint-Michel-d'Aurance", +"Saint-Michel-d'Euzet", +"Saint-Michel-d'Halescourt", +"Saint-Michel-de-Bannières", +"Saint-Michel-de-Boulogne", +"Saint-Michel-de-Castelnau", +"Saint-Michel-de-Chabrillanoux", +"Saint-Michel-de-Chaillol", +"Saint-Michel-de-Chavaignes", +"Saint-Michel-de-Double", +"Saint-Michel-de-Dèze", +"Saint-Michel-de-Feins", +"Saint-Michel-de-Fronsac", +"Saint-Michel-de-Lanès", +"Saint-Michel-de-Lapujade", +"Saint-Michel-de-Llotes", +"Saint-Michel-de-Maurienne", +"Saint-Michel-de-Montaigne", +"Saint-Michel-de-Montjoie", +"Saint-Michel-de-Plélan", +"Saint-Michel-de-Rieufret", +"Saint-Michel-de-Saint-Geoirs", +"Saint-Michel-de-Vax", +"Saint-Michel-de-Veisse", +"Saint-Michel-de-Villadeix", +"Saint-Michel-de-Volangis", +"Saint-Michel-de-la-Pierre", +"Saint-Michel-de-la-Roë", +"Saint-Michel-en-Beaumont", +"Saint-Michel-en-Brenne", +"Saint-Michel-en-Grève", +"Saint-Michel-en-l'Herm", +"Saint-Michel-et-Chanveaux", +"Saint-Michel-l'Observatoire", +"Saint-Michel-le-Cloucq", +"Saint-Michel-les-Portes", +"Saint-Michel-sous-Bois", +"Saint-Michel-sur-Loire", +"Saint-Michel-sur-Meurthe", +"Saint-Michel-sur-Orge", +"Saint-Michel-sur-Rhône", +"Saint-Michel-sur-Savasse", +"Saint-Michel-sur-Ternoise", +"Saint-Mihiel", +"Saint-Mitre-les-Remparts", +"Saint-Molf", +"Saint-Momelin", +"Saint-Mont", +"Saint-Montan", +"Saint-Moreil", +"Saint-Morel", +"Saint-Morillon", +"Saint-Moré", +"Saint-Mury-Monteymond", +"Saint-Myon", +"Saint-Méard", +"Saint-Méard-de-Drône", +"Saint-Méard-de-Gurçon", +"Saint-Médard", +"Saint-Médard-Nicourby", +"Saint-Médard-d'Aunis", +"Saint-Médard-d'Excideuil", +"Saint-Médard-d'Eyrans", +"Saint-Médard-de-Guizières", +"Saint-Médard-de-Mussidan", +"Saint-Médard-de-Presque", +"Saint-Médard-en-Forez", +"Saint-Médard-en-Jalles", +"Saint-Médard-la-Rochette", +"Saint-Médard-sur-Ille", +"Saint-Méen", +"Saint-Méen-le-Grand", +"Saint-Mélany", +"Saint-Méloir-des-Bois", +"Saint-Méloir-des-Ondes", +"Saint-Méry", +"Saint-Mézard", +"Saint-Même-les-Carrières", +"Saint-Nabor", +"Saint-Nabord", +"Saint-Nabord-sur-Aube", +"Saint-Nauphary", +"Saint-Nazaire", +"Saint-Nazaire-d'Aude", +"Saint-Nazaire-de-Ladarez", +"Saint-Nazaire-de-Pézan", +"Saint-Nazaire-de-Valentane", +"Saint-Nazaire-des-Gardies", +"Saint-Nazaire-en-Royans", +"Saint-Nazaire-le-Désert", +"Saint-Nazaire-les-Eymes", +"Saint-Nazaire-sur-Charente", +"Saint-Nectaire", +"Saint-Nexans", +"Saint-Nic", +"Saint-Nicodème", +"Saint-Nicolas", +"Saint-Nicolas-aux-Bois", +"Saint-Nicolas-d'Aliermont", +"Saint-Nicolas-de-Bourgueil", +"Saint-Nicolas-de-Macherin", +"Saint-Nicolas-de-Pierrepont", +"Saint-Nicolas-de-Port", +"Saint-Nicolas-de-Redon", +"Saint-Nicolas-de-Sommaire", +"Saint-Nicolas-de-la-Balerme", +"Saint-Nicolas-de-la-Grave", +"Saint-Nicolas-de-la-Haie", +"Saint-Nicolas-de-la-Taille", +"Saint-Nicolas-des-Biefs", +"Saint-Nicolas-des-Bois", +"Saint-Nicolas-des-Motets", +"Saint-Nicolas-du-Pélem", +"Saint-Nicolas-du-Tertre", +"Saint-Nicolas-la-Chapelle", +"Saint-Nicolas-lès-Cîteaux", +"Saint-Nizier-d'Azergues", +"Saint-Nizier-de-Fornas", +"Saint-Nizier-du-Moucherotte", +"Saint-Nizier-le-Bouchoux", +"Saint-Nizier-le-Désert", +"Saint-Nizier-sous-Charlieu", +"Saint-Nizier-sur-Arroux", +"Saint-Nolff", +"Saint-Nom-la-Bretèche", +"Saint-Offenge", +"Saint-Omer", +"Saint-Omer-Capelle", +"Saint-Omer-en-Chaussée", +"Saint-Ondras", +"Saint-Onen-la-Chapelle", +"Saint-Oradoux-de-Chirouze", +"Saint-Oradoux-près-Crocq", +"Saint-Orens", +"Saint-Orens-Pouy-Petit", +"Saint-Orens-de-Gameville", +"Saint-Ost", +"Saint-Ouen", +"Saint-Ouen-Domprot", +"Saint-Ouen-Marchefroy", +"Saint-Ouen-d'Aunis", +"Saint-Ouen-de-Mimbré", +"Saint-Ouen-de-Pontcheuil", +"Saint-Ouen-de-Sécherouvre", +"Saint-Ouen-de-Thouberville", +"Saint-Ouen-de-la-Cour", +"Saint-Ouen-des-Alleux", +"Saint-Ouen-des-Champs", +"Saint-Ouen-du-Breuil", +"Saint-Ouen-du-Mesnil-Oger", +"Saint-Ouen-du-Tilleul", +"Saint-Ouen-en-Belin", +"Saint-Ouen-en-Brie", +"Saint-Ouen-en-Champagne", +"Saint-Ouen-l'Aumône", +"Saint-Ouen-la-Rouërie", +"Saint-Ouen-la-Thène", +"Saint-Ouen-le-Brisoult", +"Saint-Ouen-le-Mauger", +"Saint-Ouen-le-Pin", +"Saint-Ouen-les-Vignes", +"Saint-Ouen-lès-Parey", +"Saint-Ouen-sous-Bailly", +"Saint-Ouen-sur-Gartempe", +"Saint-Ouen-sur-Iton", +"Saint-Ouen-sur-Loire", +"Saint-Ouen-sur-Morin", +"Saint-Oulph", +"Saint-Ours", +"Saint-Outrille", +"Saint-Ouën-des-Toits", +"Saint-Ouën-des-Vallons", +"Saint-Ovin", +"Saint-Oyen", +"Saint-Pabu", +"Saint-Pair", +"Saint-Pair-sur-Mer", +"Saint-Pal-de-Chalencon", +"Saint-Pal-de-Mons", +"Saint-Pal-de-Senouire", +"Saint-Palais", +"Saint-Palais-de-Négrignac", +"Saint-Palais-de-Phiolin", +"Saint-Palais-du-Né", +"Saint-Palais-sur-Mer", +"Saint-Pancrace", +"Saint-Pancrasse", +"Saint-Pancré", +"Saint-Pandelon", +"Saint-Pantaly-d'Ans", +"Saint-Pantaly-d'Excideuil", +"Saint-Pantaléon", +"Saint-Pantaléon-de-Lapleau", +"Saint-Pantaléon-de-Larche", +"Saint-Pantaléon-les-Vignes", +"Saint-Papoul", +"Saint-Pardon-de-Conques", +"Saint-Pardoult", +"Saint-Pardoux", +"Saint-Pardoux-Corbier", +"Saint-Pardoux-Isaac", +"Saint-Pardoux-Morterolles", +"Saint-Pardoux-d'Arnet", +"Saint-Pardoux-de-Drône", +"Saint-Pardoux-du-Breuil", +"Saint-Pardoux-et-Vielvic", +"Saint-Pardoux-l'Ortigier", +"Saint-Pardoux-la-Croisille", +"Saint-Pardoux-la-Rivière", +"Saint-Pardoux-le-Neuf", +"Saint-Pardoux-le-Vieux", +"Saint-Pardoux-les-Cards", +"Saint-Pargoire", +"Saint-Parize-en-Viry", +"Saint-Parize-le-Châtel", +"Saint-Parres-aux-Tertres", +"Saint-Parres-lès-Vaudes", +"Saint-Parthem", +"Saint-Pastour", +"Saint-Pastous", +"Saint-Paterne", +"Saint-Paterne-Racan", +"Saint-Pathus", +"Saint-Patrice", +"Saint-Patrice-de-Claids", +"Saint-Patrice-du-Désert", +"Saint-Paul", +"Saint-Paul - Flaugnac", +"Saint-Paul-Cap-de-Joux", +"Saint-Paul-Lizonne", +"Saint-Paul-Mont-Penit", +"Saint-Paul-Trois-Châteaux", +"Saint-Paul-aux-Bois", +"Saint-Paul-d'Espis", +"Saint-Paul-d'Izeaux", +"Saint-Paul-d'Oueil", +"Saint-Paul-d'Uzore", +"Saint-Paul-de-Baïse", +"Saint-Paul-de-Fenouillet", +"Saint-Paul-de-Fourques", +"Saint-Paul-de-Jarrat", +"Saint-Paul-de-Salers", +"Saint-Paul-de-Serre", +"Saint-Paul-de-Tartas", +"Saint-Paul-de-Varax", +"Saint-Paul-de-Varces", +"Saint-Paul-de-Vence", +"Saint-Paul-de-Vern", +"Saint-Paul-de-Vézelin", +"Saint-Paul-des-Landes", +"Saint-Paul-du-Bois", +"Saint-Paul-du-Vernay", +"Saint-Paul-en-Born", +"Saint-Paul-en-Chablais", +"Saint-Paul-en-Cornillon", +"Saint-Paul-en-Forêt", +"Saint-Paul-en-Gâtine", +"Saint-Paul-en-Jarez", +"Saint-Paul-en-Pareds", +"Saint-Paul-et-Valmalle", +"Saint-Paul-la-Coste", +"Saint-Paul-la-Roche", +"Saint-Paul-le-Froid", +"Saint-Paul-le-Gaultier", +"Saint-Paul-le-Jeune", +"Saint-Paul-les-Fonts", +"Saint-Paul-lès-Dax", +"Saint-Paul-lès-Durance", +"Saint-Paul-lès-Monestier", +"Saint-Paul-lès-Romans", +"Saint-Paul-sur-Isère", +"Saint-Paul-sur-Save", +"Saint-Paul-sur-Ubaye", +"Saint-Paulet", +"Saint-Paulet-de-Caisson", +"Saint-Paulien", +"Saint-Pavace", +"Saint-Paër", +"Saint-Pellerin", +"Saint-Perdon", +"Saint-Perdoux", +"Saint-Pern", +"Saint-Perreux", +"Saint-Pey-d'Armens", +"Saint-Pey-de-Castets", +"Saint-Phal", +"Saint-Philbert-de-Bouaine", +"Saint-Philbert-de-Grand-Lieu", +"Saint-Philbert-des-Champs", +"Saint-Philbert-du-Peuple", +"Saint-Philbert-sur-Boissey", +"Saint-Philbert-sur-Orne", +"Saint-Philbert-sur-Risle", +"Saint-Philibert", +"Saint-Philippe", +"Saint-Philippe-d'Aiguille", +"Saint-Philippe-du-Seignal", +"Saint-Piat", +"Saint-Pierre", +"Saint-Pierre-Aigle", +"Saint-Pierre-Avez", +"Saint-Pierre-Azif", +"Saint-Pierre-Bellevue", +"Saint-Pierre-Bois", +"Saint-Pierre-Brouck", +"Saint-Pierre-Bénouville", +"Saint-Pierre-Canivet", +"Saint-Pierre-Chérignat", +"Saint-Pierre-Colamine", +"Saint-Pierre-Eynac", +"Saint-Pierre-Lafeuille", +"Saint-Pierre-Langers", +"Saint-Pierre-Laval", +"Saint-Pierre-Lavis", +"Saint-Pierre-Quiberon", +"Saint-Pierre-Roche", +"Saint-Pierre-Saint-Jean", +"Saint-Pierre-Toirac", +"Saint-Pierre-d'Albigny", +"Saint-Pierre-d'Alvey", +"Saint-Pierre-d'Amilly", +"Saint-Pierre-d'Argençon", +"Saint-Pierre-d'Arthéglise", +"Saint-Pierre-d'Aubézies", +"Saint-Pierre-d'Aurillac", +"Saint-Pierre-d'Autils", +"Saint-Pierre-d'Entremont", +"Saint-Pierre-d'Exideuil", +"Saint-Pierre-d'Eyraud", +"Saint-Pierre-d'Irube", +"Saint-Pierre-d'Oléron", +"Saint-Pierre-de-Bailleul", +"Saint-Pierre-de-Bat", +"Saint-Pierre-de-Belleville", +"Saint-Pierre-de-Bressieux", +"Saint-Pierre-de-Buzet", +"Saint-Pierre-de-Bœuf", +"Saint-Pierre-de-Cernières", +"Saint-Pierre-de-Chandieu", +"Saint-Pierre-de-Chartreuse", +"Saint-Pierre-de-Chevillé", +"Saint-Pierre-de-Chignac", +"Saint-Pierre-de-Chérennes", +"Saint-Pierre-de-Clairac", +"Saint-Pierre-de-Colombier", +"Saint-Pierre-de-Cormeilles", +"Saint-Pierre-de-Coutances", +"Saint-Pierre-de-Curtille", +"Saint-Pierre-de-Côle", +"Saint-Pierre-de-Frugie", +"Saint-Pierre-de-Fursac", +"Saint-Pierre-de-Genebroz", +"Saint-Pierre-de-Jards", +"Saint-Pierre-de-Juillers", +"Saint-Pierre-de-Lages", +"Saint-Pierre-de-Lamps", +"Saint-Pierre-de-Maillé", +"Saint-Pierre-de-Manneville", +"Saint-Pierre-de-Mons", +"Saint-Pierre-de-Méaroz", +"Saint-Pierre-de-Mésage", +"Saint-Pierre-de-Mézoargues", +"Saint-Pierre-de-Nogaret", +"Saint-Pierre-de-Plesguen", +"Saint-Pierre-de-Rivière", +"Saint-Pierre-de-Salerne", +"Saint-Pierre-de-Semilly", +"Saint-Pierre-de-Soucy", +"Saint-Pierre-de-Trivisy", +"Saint-Pierre-de-Varengeville", +"Saint-Pierre-de-Varennes", +"Saint-Pierre-de-Vassols", +"Saint-Pierre-de-l'Isle", +"Saint-Pierre-de-la-Fage", +"Saint-Pierre-dels-Forcats", +"Saint-Pierre-des-Bois", +"Saint-Pierre-des-Champs", +"Saint-Pierre-des-Corps", +"Saint-Pierre-des-Fleurs", +"Saint-Pierre-des-Ifs", +"Saint-Pierre-des-Jonquières", +"Saint-Pierre-des-Landes", +"Saint-Pierre-des-Loges", +"Saint-Pierre-des-Nids", +"Saint-Pierre-des-Ormes", +"Saint-Pierre-des-Tripiers", +"Saint-Pierre-des-Échaubrognes", +"Saint-Pierre-du-Bosguérard", +"Saint-Pierre-du-Bû", +"Saint-Pierre-du-Champ", +"Saint-Pierre-du-Chemin", +"Saint-Pierre-du-Fresne", +"Saint-Pierre-du-Jonquet", +"Saint-Pierre-du-Lorouër", +"Saint-Pierre-du-Mont", +"Saint-Pierre-du-Palais", +"Saint-Pierre-du-Perray", +"Saint-Pierre-du-Regard", +"Saint-Pierre-du-Val", +"Saint-Pierre-du-Vauvray", +"Saint-Pierre-en-Faucigny", +"Saint-Pierre-en-Port", +"Saint-Pierre-en-Val", +"Saint-Pierre-en-Vaux", +"Saint-Pierre-es-Champs", +"Saint-Pierre-la-Bourlhonne", +"Saint-Pierre-la-Bruyère", +"Saint-Pierre-la-Cour", +"Saint-Pierre-la-Garenne", +"Saint-Pierre-la-Noaille", +"Saint-Pierre-la-Palud", +"Saint-Pierre-la-Rivière", +"Saint-Pierre-la-Roche", +"Saint-Pierre-le-Bost", +"Saint-Pierre-le-Chastel", +"Saint-Pierre-le-Moûtier", +"Saint-Pierre-le-Vieux", +"Saint-Pierre-le-Viger", +"Saint-Pierre-les-Bois", +"Saint-Pierre-les-Étieux", +"Saint-Pierre-lès-Bitry", +"Saint-Pierre-lès-Elbeuf", +"Saint-Pierre-lès-Franqueville", +"Saint-Pierre-lès-Nemours", +"Saint-Pierre-sur-Dives", +"Saint-Pierre-sur-Doux", +"Saint-Pierre-sur-Dropt", +"Saint-Pierre-sur-Erve", +"Saint-Pierre-sur-Orthe", +"Saint-Pierre-sur-Vence", +"Saint-Pierre-Église", +"Saint-Pierre-à-Arnes", +"Saint-Pierremont", +"Saint-Pierreville", +"Saint-Pierrevillers", +"Saint-Plaisir", +"Saint-Plancard", +"Saint-Planchers", +"Saint-Plantaire", +"Saint-Point", +"Saint-Point-Lac", +"Saint-Pois", +"Saint-Poix", +"Saint-Pol-de-Léon", +"Saint-Pol-sur-Ternoise", +"Saint-Polgues", +"Saint-Polycarpe", +"Saint-Pompain", +"Saint-Pompont", +"Saint-Poncy", +"Saint-Pons", +"Saint-Pons-de-Mauchiens", +"Saint-Pons-de-Thomières", +"Saint-Pons-la-Calm", +"Saint-Pont", +"Saint-Porchaire", +"Saint-Porquier", +"Saint-Pouange", +"Saint-Pourçain-sur-Besbre", +"Saint-Pourçain-sur-Sioule", +"Saint-Prancher", +"Saint-Prest", +"Saint-Preuil", +"Saint-Priest", +"Saint-Priest-Bramefant", +"Saint-Priest-Ligoure", +"Saint-Priest-Palus", +"Saint-Priest-Taurion", +"Saint-Priest-d'Andelot", +"Saint-Priest-de-Gimel", +"Saint-Priest-des-Champs", +"Saint-Priest-en-Jarez", +"Saint-Priest-en-Murat", +"Saint-Priest-la-Feuille", +"Saint-Priest-la-Marche", +"Saint-Priest-la-Plaine", +"Saint-Priest-la-Prugne", +"Saint-Priest-la-Roche", +"Saint-Priest-la-Vêtre", +"Saint-Priest-les-Fougères", +"Saint-Priest-sous-Aixe", +"Saint-Prim", +"Saint-Privat", +"Saint-Privat-d'Allier", +"Saint-Privat-de-Champclos", +"Saint-Privat-de-Vallongue", +"Saint-Privat-des-Prés", +"Saint-Privat-des-Vieux", +"Saint-Privat-du-Dragon", +"Saint-Privat-du-Fau", +"Saint-Privat-la-Montagne", +"Saint-Privé", +"Saint-Prix", +"Saint-Prix-lès-Arnay", +"Saint-Projet", +"Saint-Projet-Saint-Constant", +"Saint-Projet-de-Salers", +"Saint-Prouant", +"Saint-Pryvé-Saint-Mesmin", +"Saint-Préjet-Armandon", +"Saint-Préjet-d'Allier", +"Saint-Puy", +"Saint-Python", +"Saint-Père", +"Saint-Père-en-Retz", +"Saint-Père-sur-Loire", +"Saint-Pé-Delbosc", +"Saint-Pé-Saint-Simon", +"Saint-Pé-d'Ardet", +"Saint-Pé-de-Bigorre", +"Saint-Pé-de-Léren", +"Saint-Pée-sur-Nivelle", +"Saint-Péran", +"Saint-Péravy-la-Colombe", +"Saint-Péray", +"Saint-Péreuse", +"Saint-Péver", +"Saint-Pôtan", +"Saint-Quantin-de-Rançanne", +"Saint-Quay-Perros", +"Saint-Quay-Portrieux", +"Saint-Quentin", +"Saint-Quentin-Fallavier", +"Saint-Quentin-de-Baron", +"Saint-Quentin-de-Blavou", +"Saint-Quentin-de-Caplong", +"Saint-Quentin-de-Chalais", +"Saint-Quentin-des-Isles", +"Saint-Quentin-des-Prés", +"Saint-Quentin-du-Dropt", +"Saint-Quentin-en-Tourmont", +"Saint-Quentin-la-Chabanne", +"Saint-Quentin-la-Motte-Croix-au-Bailly", +"Saint-Quentin-la-Poterie", +"Saint-Quentin-la-Tour", +"Saint-Quentin-le-Petit", +"Saint-Quentin-le-Verger", +"Saint-Quentin-les-Anges", +"Saint-Quentin-les-Chardonnets", +"Saint-Quentin-les-Marais", +"Saint-Quentin-sur-Charente", +"Saint-Quentin-sur-Coole", +"Saint-Quentin-sur-Indrois", +"Saint-Quentin-sur-Isère", +"Saint-Quentin-sur-Nohain", +"Saint-Quentin-sur-Sauxillanges", +"Saint-Quentin-sur-le-Homme", +"Saint-Quintin-sur-Sioule", +"Saint-Quirc", +"Saint-Quirin", +"Saint-Rabier", +"Saint-Racho", +"Saint-Rambert-d'Albon", +"Saint-Rambert-en-Bugey", +"Saint-Raphaël", +"Saint-Remimont", +"Saint-Remy", +"Saint-Remy-Chaussée", +"Saint-Remy-du-Nord", +"Saint-Remy-en-Bouzemont-Saint-Genest-et-Isson", +"Saint-Remy-en-l'Eau", +"Saint-Remy-la-Calonne", +"Saint-Remy-le-Petit", +"Saint-Remy-sous-Barbuise", +"Saint-Remy-sous-Broyes", +"Saint-Remy-sur-Bussy", +"Saint-Remèze", +"Saint-Renan", +"Saint-Restitut", +"Saint-Rieul", +"Saint-Rimay", +"Saint-Riquier", +"Saint-Riquier-en-Rivière", +"Saint-Riquier-ès-Plains", +"Saint-Rirand", +"Saint-Rivoal", +"Saint-Robert", +"Saint-Roch", +"Saint-Roch-sur-Égrenne", +"Saint-Rogatien", +"Saint-Romain", +"Saint-Romain-Lachalm", +"Saint-Romain-au-Mont-d'Or", +"Saint-Romain-d'Ay", +"Saint-Romain-d'Urfé", +"Saint-Romain-de-Benet", +"Saint-Romain-de-Colbosc", +"Saint-Romain-de-Jalionas", +"Saint-Romain-de-Lerps", +"Saint-Romain-de-Monpazier", +"Saint-Romain-de-Popey", +"Saint-Romain-de-Surieu", +"Saint-Romain-en-Gal", +"Saint-Romain-en-Gier", +"Saint-Romain-en-Jarez", +"Saint-Romain-en-Viennois", +"Saint-Romain-et-Saint-Clément", +"Saint-Romain-la-Motte", +"Saint-Romain-la-Virvée", +"Saint-Romain-le-Noble", +"Saint-Romain-le-Puy", +"Saint-Romain-les-Atheux", +"Saint-Romain-sous-Gourdon", +"Saint-Romain-sous-Versigny", +"Saint-Romain-sur-Cher", +"Saint-Romain-sur-Gironde", +"Saint-Roman", +"Saint-Roman-de-Codières", +"Saint-Roman-de-Malegarde", +"Saint-Romans", +"Saint-Romans-des-Champs", +"Saint-Romans-lès-Melle", +"Saint-Rome", +"Saint-Rome-de-Cernon", +"Saint-Rome-de-Dolan", +"Saint-Rome-de-Tarn", +"Saint-Rustice", +"Saint-Règle", +"Saint-Régis-du-Coin", +"Saint-Rémy", +"Saint-Rémy-Blanzy", +"Saint-Rémy-Boscrocourt", +"Saint-Rémy-au-Bois", +"Saint-Rémy-aux-Bois", +"Saint-Rémy-de-Blot", +"Saint-Rémy-de-Chargnat", +"Saint-Rémy-de-Chaudes-Aigues", +"Saint-Rémy-de-Maurienne", +"Saint-Rémy-de-Provence", +"Saint-Rémy-de-Sillé", +"Saint-Rémy-des-Monts", +"Saint-Rémy-du-Plain", +"Saint-Rémy-du-Val", +"Saint-Rémy-en-Rollat", +"Saint-Rémy-l'Honoré", +"Saint-Rémy-la-Vanne", +"Saint-Rémy-la-Varenne", +"Saint-Rémy-lès-Chevreuse", +"Saint-Rémy-sur-Avre", +"Saint-Rémy-sur-Creuse", +"Saint-Rémy-sur-Durolle", +"Saint-Révérend", +"Saint-Révérien", +"Saint-Saire", +"Saint-Salvadour", +"Saint-Salvi-de-Carcavès", +"Saint-Salvy", +"Saint-Salvy-de-la-Balme", +"Saint-Samson", +"Saint-Samson-de-la-Roque", +"Saint-Samson-la-Poterie", +"Saint-Samson-sur-Rance", +"Saint-Sandoux", +"Saint-Santin", +"Saint-Santin-Cantalès", +"Saint-Santin-de-Maurs", +"Saint-Sardos", +"Saint-Satur", +"Saint-Saturnin", +"Saint-Saturnin-de-Lenne", +"Saint-Saturnin-de-Lucian", +"Saint-Saturnin-du-Bois", +"Saint-Saturnin-du-Limet", +"Saint-Saturnin-lès-Apt", +"Saint-Saturnin-lès-Avignon", +"Saint-Saturnin-sur-Loire", +"Saint-Saud-Lacoussière", +"Saint-Sauflieu", +"Saint-Saulge", +"Saint-Saulve", +"Saint-Saury", +"Saint-Sauvant", +"Saint-Sauves-d'Auvergne", +"Saint-Sauveur", +"Saint-Sauveur-Camprieu", +"Saint-Sauveur-Gouvernet", +"Saint-Sauveur-Lalande", +"Saint-Sauveur-Lendelin", +"Saint-Sauveur-Marville", +"Saint-Sauveur-d'Aunis", +"Saint-Sauveur-d'Émalleville", +"Saint-Sauveur-de-Carrouges", +"Saint-Sauveur-de-Cruzières", +"Saint-Sauveur-de-Flée", +"Saint-Sauveur-de-Ginestoux", +"Saint-Sauveur-de-Meilhan", +"Saint-Sauveur-de-Montagut", +"Saint-Sauveur-de-Peyre", +"Saint-Sauveur-de-Pierrepont", +"Saint-Sauveur-de-Puynormand", +"Saint-Sauveur-des-Landes", +"Saint-Sauveur-en-Diois", +"Saint-Sauveur-en-Puisaye", +"Saint-Sauveur-en-Rue", +"Saint-Sauveur-la-Pommeraye", +"Saint-Sauveur-la-Sagne", +"Saint-Sauveur-le-Vicomte", +"Saint-Sauveur-lès-Bray", +"Saint-Sauveur-sur-Tinée", +"Saint-Sauveur-sur-École", +"Saint-Sauvier", +"Saint-Sauvy", +"Saint-Savin", +"Saint-Savinien", +"Saint-Saviol", +"Saint-Savournin", +"Saint-Saëns", +"Saint-Secondin", +"Saint-Seine", +"Saint-Seine-en-Bâche", +"Saint-Seine-l'Abbaye", +"Saint-Seine-sur-Vingeanne", +"Saint-Selve", +"Saint-Senier-de-Beuvron", +"Saint-Senier-sous-Avranches", +"Saint-Senoch", +"Saint-Senoux", +"Saint-Sernin", +"Saint-Sernin-du-Bois", +"Saint-Sernin-du-Plain", +"Saint-Sernin-lès-Lavaur", +"Saint-Sernin-sur-Rance", +"Saint-Servais", +"Saint-Servant", +"Saint-Setiers", +"Saint-Seurin-de-Bourg", +"Saint-Seurin-de-Cadourne", +"Saint-Seurin-de-Cursac", +"Saint-Seurin-de-Palenne", +"Saint-Seurin-de-Prats", +"Saint-Seurin-sur-l'Isle", +"Saint-Sever", +"Saint-Sever-Calvados", +"Saint-Sever-de-Rustan", +"Saint-Sever-de-Saintonge", +"Saint-Sever-du-Moustier", +"Saint-Siffret", +"Saint-Sigismond", +"Saint-Sigismond-de-Clermont", +"Saint-Silvain-Bas-le-Roc", +"Saint-Silvain-Bellegarde", +"Saint-Silvain-Montaigut", +"Saint-Silvain-sous-Toulx", +"Saint-Simeux", +"Saint-Simon", +"Saint-Simon-de-Bordes", +"Saint-Simon-de-Pellouaille", +"Saint-Siméon", +"Saint-Siméon-de-Bressieux", +"Saint-Sixt", +"Saint-Sixte", +"Saint-Solve", +"Saint-Sorlin", +"Saint-Sorlin-d'Arves", +"Saint-Sorlin-de-Conac", +"Saint-Sorlin-de-Morestel", +"Saint-Sorlin-de-Vienne", +"Saint-Sorlin-en-Bugey", +"Saint-Sorlin-en-Valloire", +"Saint-Sornin", +"Saint-Sornin-Lavolps", +"Saint-Sornin-Leulac", +"Saint-Sornin-la-Marche", +"Saint-Soulan", +"Saint-Souplet", +"Saint-Souplet-sur-Py", +"Saint-Soupplets", +"Saint-Sozy", +"Saint-Stail", +"Saint-Suliac", +"Saint-Sulpice", +"Saint-Sulpice-Laurière", +"Saint-Sulpice-d'Arnoult", +"Saint-Sulpice-d'Excideuil", +"Saint-Sulpice-de-Cognac", +"Saint-Sulpice-de-Faleyrens", +"Saint-Sulpice-de-Favières", +"Saint-Sulpice-de-Grimbouville", +"Saint-Sulpice-de-Guilleragues", +"Saint-Sulpice-de-Mareuil", +"Saint-Sulpice-de-Pommeray", +"Saint-Sulpice-de-Pommiers", +"Saint-Sulpice-de-Roumagnac", +"Saint-Sulpice-de-Royan", +"Saint-Sulpice-de-Ruffec", +"Saint-Sulpice-des-Landes", +"Saint-Sulpice-des-Rivoires", +"Saint-Sulpice-en-Pareds", +"Saint-Sulpice-et-Cameyrac", +"Saint-Sulpice-la-Forêt", +"Saint-Sulpice-la-Pointe", +"Saint-Sulpice-le-Dunois", +"Saint-Sulpice-le-Guérétois", +"Saint-Sulpice-les-Bois", +"Saint-Sulpice-les-Champs", +"Saint-Sulpice-les-Feuilles", +"Saint-Sulpice-sur-Lèze", +"Saint-Sulpice-sur-Risle", +"Saint-Supplet", +"Saint-Sylvain", +"Saint-Sylvestre", +"Saint-Sylvestre-Cappel", +"Saint-Sylvestre-Pragoulin", +"Saint-Sylvestre-de-Cormeilles", +"Saint-Sylvestre-sur-Lot", +"Saint-Symphorien", +"Saint-Symphorien-d'Ancelles", +"Saint-Symphorien-d'Ozon", +"Saint-Symphorien-de-Lay", +"Saint-Symphorien-de-Mahun", +"Saint-Symphorien-de-Marmagne", +"Saint-Symphorien-de-Thénières", +"Saint-Symphorien-des-Bois", +"Saint-Symphorien-des-Bruyères", +"Saint-Symphorien-sous-Chomérac", +"Saint-Symphorien-sur-Coise", +"Saint-Symphorien-sur-Couze", +"Saint-Symphorien-sur-Saône", +"Saint-Sève", +"Saint-Sébastien", +"Saint-Sébastien-d'Aigrefeuille", +"Saint-Sébastien-de-Morsent", +"Saint-Sébastien-de-Raids", +"Saint-Sébastien-sur-Loire", +"Saint-Ségal", +"Saint-Séglin", +"Saint-Sériès", +"Saint-Sérotin", +"Saint-Séverin", +"Saint-Séverin-d'Estissac", +"Saint-Séverin-sur-Boutonne", +"Saint-Thegonnec Loc-Eguiner", +"Saint-Thibaud-de-Couz", +"Saint-Thibault", +"Saint-Thibault-des-Vignes", +"Saint-Thibaut", +"Saint-Thibéry", +"Saint-Thierry", +"Saint-Thiébaud", +"Saint-Thiébault", +"Saint-Thois", +"Saint-Thomas", +"Saint-Thomas-de-Conac", +"Saint-Thomas-de-Courceriers", +"Saint-Thomas-en-Argonne", +"Saint-Thomas-en-Royans", +"Saint-Thomas-la-Garde", +"Saint-Thomé", +"Saint-Thonan", +"Saint-Thual", +"Saint-Thurial", +"Saint-Thuriau", +"Saint-Thurien", +"Saint-Thurin", +"Saint-Thélo", +"Saint-Théodorit", +"Saint-Théoffrey", +"Saint-Tricat", +"Saint-Trimoël", +"Saint-Trinit", +"Saint-Trivier-de-Courtes", +"Saint-Trivier-sur-Moignans", +"Saint-Trojan", +"Saint-Trojan-les-Bains", +"Saint-Tropez", +"Saint-Tugdual", +"Saint-Ulphace", +"Saint-Ulrich", +"Saint-Uniac", +"Saint-Urbain", +"Saint-Urbain-Maconcourt", +"Saint-Urcisse", +"Saint-Urcize", +"Saint-Usage", +"Saint-Usuge", +"Saint-Utin", +"Saint-Uze", +"Saint-Vaast-Dieppedalle", +"Saint-Vaast-d'Équiqueville", +"Saint-Vaast-de-Longmont", +"Saint-Vaast-du-Val", +"Saint-Vaast-en-Auge", +"Saint-Vaast-en-Cambrésis", +"Saint-Vaast-en-Chaussée", +"Saint-Vaast-la-Hougue", +"Saint-Vaast-lès-Mello", +"Saint-Vaast-sur-Seulles", +"Saint-Vaize", +"Saint-Valbert", +"Saint-Valentin", +"Saint-Valery", +"Saint-Valery-en-Caux", +"Saint-Valery-sur-Somme", +"Saint-Vallerin", +"Saint-Vallier", +"Saint-Vallier-de-Thiey", +"Saint-Vallier-sur-Marne", +"Saint-Valérien", +"Saint-Varent", +"Saint-Vaury", +"Saint-Venant", +"Saint-Vert", +"Saint-Viance", +"Saint-Viaud", +"Saint-Victeur", +"Saint-Victor", +"Saint-Victor-Malescours", +"Saint-Victor-Montvianeix", +"Saint-Victor-Rouzaud", +"Saint-Victor-d'Épine", +"Saint-Victor-de-Buthon", +"Saint-Victor-de-Cessieu", +"Saint-Victor-de-Chrétienville", +"Saint-Victor-de-Malcap", +"Saint-Victor-de-Morestel", +"Saint-Victor-des-Oules", +"Saint-Victor-en-Marche", +"Saint-Victor-et-Melvieu", +"Saint-Victor-l'Abbaye", +"Saint-Victor-la-Coste", +"Saint-Victor-la-Rivière", +"Saint-Victor-sur-Arlanc", +"Saint-Victor-sur-Avre", +"Saint-Victor-sur-Ouche", +"Saint-Victor-sur-Rhins", +"Saint-Victoret", +"Saint-Victour", +"Saint-Victurnien", +"Saint-Vidal", +"Saint-Vigor", +"Saint-Vigor-d'Ymonville", +"Saint-Vigor-des-Monts", +"Saint-Vigor-des-Mézerets", +"Saint-Vigor-le-Grand", +"Saint-Vincent", +"Saint-Vincent-Bragny", +"Saint-Vincent-Cramesnil", +"Saint-Vincent-Jalmoutiers", +"Saint-Vincent-Lespinasse", +"Saint-Vincent-Rive-d'Olt", +"Saint-Vincent-Sterlanges", +"Saint-Vincent-d'Autéjac", +"Saint-Vincent-d'Olargues", +"Saint-Vincent-de-Barbeyrargues", +"Saint-Vincent-de-Barrès", +"Saint-Vincent-de-Boisset", +"Saint-Vincent-de-Connezac", +"Saint-Vincent-de-Cosse", +"Saint-Vincent-de-Durfort", +"Saint-Vincent-de-Lamontjoie", +"Saint-Vincent-de-Mercuze", +"Saint-Vincent-de-Paul", +"Saint-Vincent-de-Pertignas", +"Saint-Vincent-de-Reins", +"Saint-Vincent-de-Salers", +"Saint-Vincent-de-Tyrosse", +"Saint-Vincent-des-Bois", +"Saint-Vincent-des-Landes", +"Saint-Vincent-des-Prés", +"Saint-Vincent-du-Boulay", +"Saint-Vincent-du-Lorouër", +"Saint-Vincent-du-Pendit", +"Saint-Vincent-en-Bresse", +"Saint-Vincent-la-Châtre", +"Saint-Vincent-la-Commanderie", +"Saint-Vincent-le-Paluel", +"Saint-Vincent-les-Forts", +"Saint-Vincent-sur-Graon", +"Saint-Vincent-sur-Jabron", +"Saint-Vincent-sur-Jard", +"Saint-Vincent-sur-Oust", +"Saint-Vincent-sur-l'Isle", +"Saint-Vit", +"Saint-Vital", +"Saint-Vite", +"Saint-Vitte", +"Saint-Vitte-sur-Briance", +"Saint-Vivien", +"Saint-Vivien-de-Blaye", +"Saint-Vivien-de-Monségur", +"Saint-Vivien-de-Médoc", +"Saint-Viâtre", +"Saint-Voir", +"Saint-Vougay", +"Saint-Vrain", +"Saint-Vran", +"Saint-Vulbas", +"Saint-Vénérand", +"Saint-Vérain", +"Saint-Véran", +"Saint-Vérand", +"Saint-Waast", +"Saint-Witz", +"Saint-Xandre", +"Saint-Yaguen", +"Saint-Yan", +"Saint-Ybard", +"Saint-Ybars", +"Saint-Yon", +"Saint-Yorre", +"Saint-Yrieix-la-Montagne", +"Saint-Yrieix-la-Perche", +"Saint-Yrieix-le-Déjalat", +"Saint-Yrieix-les-Bois", +"Saint-Yrieix-sous-Aixe", +"Saint-Yrieix-sur-Charente", +"Saint-Ythaire", +"Saint-Yvi", +"Saint-Yvoine", +"Saint-Yzan-de-Soudiac", +"Saint-Yzans-de-Médoc", +"Saint-Zacharie", +"Saint-Ébremond-de-Bonfossé", +"Saint-Égrève", +"Saint-Élie", +"Saint-Élier", +"Saint-Éliph", +"Saint-Élix", +"Saint-Élix-Séglan", +"Saint-Élix-Theux", +"Saint-Élix-le-Château", +"Saint-Éloi", +"Saint-Éloi-de-Fourques", +"Saint-Éloy-d'Allier", +"Saint-Éloy-de-Gy", +"Saint-Éloy-la-Glacière", +"Saint-Éloy-les-Mines", +"Saint-Éloy-les-Tuileries", +"Saint-Éman", +"Saint-Émiland", +"Saint-Émilion", +"Saint-Épain", +"Saint-Étienne", +"Saint-Étienne-Cantalès", +"Saint-Étienne-Estréchoux", +"Saint-Étienne-Lardeyrol", +"Saint-Étienne-Roilaye", +"Saint-Étienne-Vallée-Française", +"Saint-Étienne-au-Mont", +"Saint-Étienne-au-Temple", +"Saint-Étienne-aux-Clos", +"Saint-Étienne-d'Albagnan", +"Saint-Étienne-d'Orthe", +"Saint-Étienne-de-Baïgorry", +"Saint-Étienne-de-Boulogne", +"Saint-Étienne-de-Brillouet", +"Saint-Étienne-de-Carlat", +"Saint-Étienne-de-Chigny", +"Saint-Étienne-de-Chomeil", +"Saint-Étienne-de-Crossey", +"Saint-Étienne-de-Cuines", +"Saint-Étienne-de-Fontbellon", +"Saint-Étienne-de-Fougères", +"Saint-Étienne-de-Fursac", +"Saint-Étienne-de-Gourgas", +"Saint-Étienne-de-Lisse", +"Saint-Étienne-de-Lugdarès", +"Saint-Étienne-de-Maurs", +"Saint-Étienne-de-Mer-Morte", +"Saint-Étienne-de-Montluc", +"Saint-Étienne-de-Puycorbier", +"Saint-Étienne-de-Saint-Geoirs", +"Saint-Étienne-de-Serre", +"Saint-Étienne-de-Tinée", +"Saint-Étienne-de-Tulmont", +"Saint-Étienne-de-Valoux", +"Saint-Étienne-de-Vicq", +"Saint-Étienne-de-Villeréal", +"Saint-Étienne-de-l'Olm", +"Saint-Étienne-des-Champs", +"Saint-Étienne-des-Guérets", +"Saint-Étienne-des-Oullières", +"Saint-Étienne-des-Sorts", +"Saint-Étienne-du-Bois", +"Saint-Étienne-du-Grès", +"Saint-Étienne-du-Gué-de-l'Isle", +"Saint-Étienne-du-Rouvray", +"Saint-Étienne-du-Valdonnez", +"Saint-Étienne-du-Vauvray", +"Saint-Étienne-du-Vigan", +"Saint-Étienne-en-Bresse", +"Saint-Étienne-en-Coglès", +"Saint-Étienne-l'Allier", +"Saint-Étienne-la-Cigogne", +"Saint-Étienne-la-Geneste", +"Saint-Étienne-la-Thillaye", +"Saint-Étienne-la-Varenne", +"Saint-Étienne-le-Laus", +"Saint-Étienne-le-Molard", +"Saint-Étienne-les-Orgues", +"Saint-Étienne-lès-Remiremont", +"Saint-Étienne-sous-Bailleul", +"Saint-Étienne-sous-Barbuise", +"Saint-Étienne-sur-Blesle", +"Saint-Étienne-sur-Chalaronne", +"Saint-Étienne-sur-Reyssouze", +"Saint-Étienne-sur-Suippe", +"Saint-Étienne-sur-Usson", +"Saint-Étienne-à-Arnes", +"Saint-Évarzec", +"Sainte-Adresse", +"Sainte-Agathe", +"Sainte-Agathe-d'Aliermont", +"Sainte-Agathe-en-Donzy", +"Sainte-Agathe-la-Bouteresse", +"Sainte-Agnès", +"Sainte-Alauzie", +"Sainte-Alvère-Saint-Laurent Les Bâtons", +"Sainte-Anastasie", +"Sainte-Anastasie-sur-Issole", +"Sainte-Anne", +"Sainte-Anne-Saint-Priest", +"Sainte-Anne-d'Auray", +"Sainte-Anne-sur-Brivet", +"Sainte-Anne-sur-Gervonde", +"Sainte-Anne-sur-Vilaine", +"Sainte-Aulde", +"Sainte-Aurence-Cazaux", +"Sainte-Austreberthe", +"Sainte-Barbe", +"Sainte-Bazeille", +"Sainte-Beuve-en-Rivière", +"Sainte-Blandine", +"Sainte-Brigitte", +"Sainte-Camelle", +"Sainte-Catherine", +"Sainte-Catherine-de-Fierbois", +"Sainte-Christie", +"Sainte-Christie-d'Armagnac", +"Sainte-Christine", +"Sainte-Colombe", +"Sainte-Colombe-de-Duras", +"Sainte-Colombe-de-Peyre", +"Sainte-Colombe-de-Villeneuve", +"Sainte-Colombe-de-la-Commanderie", +"Sainte-Colombe-des-Bois", +"Sainte-Colombe-en-Auxois", +"Sainte-Colombe-en-Bruilhois", +"Sainte-Colombe-la-Commanderie", +"Sainte-Colombe-près-Vernon", +"Sainte-Colombe-sur-Gand", +"Sainte-Colombe-sur-Guette", +"Sainte-Colombe-sur-Loing", +"Sainte-Colombe-sur-Seine", +"Sainte-Colombe-sur-l'Hers", +"Sainte-Colome", +"Sainte-Consorce", +"Sainte-Croix", +"Sainte-Croix-Grand-Tonne", +"Sainte-Croix-Hague", +"Sainte-Croix-Vallée-Française", +"Sainte-Croix-Volvestre", +"Sainte-Croix-aux-Mines", +"Sainte-Croix-de-Caderle", +"Sainte-Croix-de-Mareuil", +"Sainte-Croix-de-Quintillargues", +"Sainte-Croix-du-Mont", +"Sainte-Croix-du-Verdon", +"Sainte-Croix-en-Jarez", +"Sainte-Croix-en-Plaine", +"Sainte-Croix-sur-Buchy", +"Sainte-Croix-sur-Mer", +"Sainte-Croix-à-Lauze", +"Sainte-Cécile", +"Sainte-Cécile-d'Andorge", +"Sainte-Cécile-du-Cayrou", +"Sainte-Cécile-les-Vignes", +"Sainte-Céronne-lès-Mortagne", +"Sainte-Cérotte", +"Sainte-Dode", +"Sainte-Eanne", +"Sainte-Engrâce", +"Sainte-Enimie", +"Sainte-Eugénie-de-Villeneuve", +"Sainte-Eulalie", +"Sainte-Eulalie-d'Ans", +"Sainte-Eulalie-d'Eymet", +"Sainte-Eulalie-d'Olt", +"Sainte-Eulalie-de-Cernon", +"Sainte-Eulalie-en-Born", +"Sainte-Eulalie-en-Royans", +"Sainte-Euphémie", +"Sainte-Euphémie-sur-Ouvèze", +"Sainte-Eusoye", +"Sainte-Fauste", +"Sainte-Feyre", +"Sainte-Feyre-la-Montagne", +"Sainte-Flaive-des-Loups", +"Sainte-Florence", +"Sainte-Florine", +"Sainte-Foi", +"Sainte-Fortunade", +"Sainte-Foy", +"Sainte-Foy-Saint-Sulpice", +"Sainte-Foy-Tarentaise", +"Sainte-Foy-d'Aigrefeuille", +"Sainte-Foy-de-Belvès", +"Sainte-Foy-de-Longas", +"Sainte-Foy-de-Peyrolières", +"Sainte-Foy-l'Argentière", +"Sainte-Foy-la-Grande", +"Sainte-Foy-la-Longue", +"Sainte-Foy-lès-Lyon", +"Sainte-Féréole", +"Sainte-Gauburge-Sainte-Colombe", +"Sainte-Gemme", +"Sainte-Gemme-Martaillac", +"Sainte-Gemme-Moronval", +"Sainte-Gemme-en-Sancerrois", +"Sainte-Gemme-la-Plaine", +"Sainte-Gemmes", +"Sainte-Gemmes-d'Andigné", +"Sainte-Gemmes-le-Robert", +"Sainte-Gemmes-sur-Loire", +"Sainte-Geneviève", +"Sainte-Geneviève-des-Bois", +"Sainte-Geneviève-lès-Gasny", +"Sainte-Hermine", +"Sainte-Honorine-de-Ducy", +"Sainte-Honorine-des-Pertes", +"Sainte-Honorine-du-Fay", +"Sainte-Honorine-la-Chardonne", +"Sainte-Honorine-la-Guillaume", +"Sainte-Hélène", +"Sainte-Hélène-Bondeville", +"Sainte-Hélène-du-Lac", +"Sainte-Hélène-sur-Isère", +"Sainte-Innocence", +"Sainte-Jalle", +"Sainte-Jamme-sur-Sarthe", +"Sainte-Julie", +"Sainte-Juliette", +"Sainte-Juliette-sur-Viaur", +"Sainte-Lheurine", +"Sainte-Livrade", +"Sainte-Livrade-sur-Lot", +"Sainte-Lizaigne", +"Sainte-Luce", +"Sainte-Luce-sur-Loire", +"Sainte-Lucie-de-Tallano", +"Sainte-Lunaise", +"Sainte-Léocadie", +"Sainte-Magnance", +"Sainte-Marguerite", +"Sainte-Marguerite-Lafigère", +"Sainte-Marguerite-d'Elle", +"Sainte-Marguerite-de-Carrouges", +"Sainte-Marguerite-de-Viette", +"Sainte-Marguerite-sur-Duclair", +"Sainte-Marguerite-sur-Fauville", +"Sainte-Marguerite-sur-Mer", +"Sainte-Marie", +"Sainte-Marie-Cappel", +"Sainte-Marie-Kerque", +"Sainte-Marie-Lapanouze", +"Sainte-Marie-Outre-l'Eau", +"Sainte-Marie-au-Bosc", +"Sainte-Marie-aux-Chênes", +"Sainte-Marie-aux-Mines", +"Sainte-Marie-d'Alloix", +"Sainte-Marie-d'Alvey", +"Sainte-Marie-d'Attez", +"Sainte-Marie-de-Chignac", +"Sainte-Marie-de-Cuines", +"Sainte-Marie-de-Gosse", +"Sainte-Marie-de-Ré", +"Sainte-Marie-de-Vatimesnil", +"Sainte-Marie-de-Vaux", +"Sainte-Marie-des-Champs", +"Sainte-Marie-du-Bois", +"Sainte-Marie-du-Lac-Nuisement", +"Sainte-Marie-du-Mont", +"Sainte-Marie-en-Chanois", +"Sainte-Marie-en-Chaux", +"Sainte-Marie-la-Blanche", +"Sainte-Marie-la-Robert", +"Sainte-Marie-sur-Ouche", +"Sainte-Marie-à-Py", +"Sainte-Marthe", +"Sainte-Maure", +"Sainte-Maure-de-Peyriac", +"Sainte-Maure-de-Touraine", +"Sainte-Maxime", +"Sainte-Menehould", +"Sainte-Mesme", +"Sainte-Mondane", +"Sainte-Montaine", +"Sainte-Mère", +"Sainte-Mère-Eglise", +"Sainte-Même", +"Sainte-Nathalène", +"Sainte-Néomaye", +"Sainte-Olive", +"Sainte-Opportune", +"Sainte-Opportune-du-Bosc", +"Sainte-Opportune-la-Mare", +"Sainte-Orse", +"Sainte-Osmane", +"Sainte-Ouenne", +"Sainte-Pallaye", +"Sainte-Paule", +"Sainte-Pazanne", +"Sainte-Pexine", +"Sainte-Preuve", +"Sainte-Pôle", +"Sainte-Radegonde", +"Sainte-Radégonde", +"Sainte-Radégonde-des-Noyers", +"Sainte-Ramée", +"Sainte-Reine", +"Sainte-Reine-de-Bretagne", +"Sainte-Rose", +"Sainte-Ruffine", +"Sainte-Sabine", +"Sainte-Sabine-sur-Longève", +"Sainte-Savine", +"Sainte-Scolasse-sur-Sarthe", +"Sainte-Segrée", +"Sainte-Sigolène", +"Sainte-Solange", +"Sainte-Soline", +"Sainte-Souline", +"Sainte-Soulle", +"Sainte-Suzanne", +"Sainte-Suzanne-et-Chammes", +"Sainte-Suzanne-sur-Vire", +"Sainte-Sève", +"Sainte-Sévère", +"Sainte-Sévère-sur-Indre", +"Sainte-Terre", +"Sainte-Thorette", +"Sainte-Thérence", +"Sainte-Trie", +"Sainte-Tréphine", +"Sainte-Tulle", +"Sainte-Valière", +"Sainte-Vaubourg", +"Sainte-Verge", +"Sainte-Vertu", +"Saintes-Maries-de-la-Mer", "Saintry-sur-Seine", +"Saints-Geosmes", +"Saints-en-Puisaye", "Saires-la-Verrerie", -"saisie-arrêt", -"saisie-attribution", -"saisie-brandon", -"saisie-exécution", -"saisie-gagerie", -"saisie-revendication", -"saisies-arrêts", -"saisies-attributions", -"saisies-brandons", -"saisies-exécutions", -"saisies-gageries", -"saisies-revendications", -"saisir-arrêter", -"saisir-brandonner", -"saisir-exécuter", -"saisir-gager", -"saisir-revendiquer", -"salafo-sioniste", -"salaire-coût", -"salaire-coûts", "Salaise-sur-Sanne", -"salamandre-tigre", "Salies-de-Béarn", "Salies-du-Salat", -"Salignac-de-Mirambeau", -"Salignac-de-Pons", "Salignac-Eyvignes", "Salignac-Eyvigues", +"Salignac-de-Mirambeau", +"Salignac-de-Pons", "Salignac-sur-Charente", "Saligny-le-Vif", "Saligny-sur-Roudon", "Salins-Fontaine", "Salins-les-Bains", "Salins-les-Thermes", -"Sallèles-Cabardès", -"Sallèles-d'Aude", -"salle-prunetais", "Salle-Prunetais", -"salle-prunetaise", "Salle-Prunetaise", -"salle-prunetaises", "Salle-Prunetaises", "Salles-Adour", "Salles-Arbuissonnas-en-Beaujolais", "Salles-Courbatiès", "Salles-Curan", +"Salles-Lavalette", +"Salles-Mongiscard", +"Salles-Sourçois", +"Salles-Sourçoise", +"Salles-Sourçoises", "Salles-d'Angles", "Salles-d'Armagnac", "Salles-d'Aude", @@ -21766,352 +15177,233 @@ FR_BASE_EXCEPTIONS = [ "Salles-en-Toulon", "Salles-et-Pratviel", "Salles-la-Source", -"Salles-Lavalette", "Salles-lès-Aulnay", -"Salles-Mongiscard", -"salles-sourçois", -"Salles-Sourçois", -"salles-sourçoise", -"Salles-Sourçoise", -"salles-sourçoises", -"Salles-Sourçoises", "Salles-sous-Bois", "Salles-sur-Garonne", -"Salles-sur-l'Hers", "Salles-sur-Mer", -"Salm-en-Vosges", +"Salles-sur-l'Hers", +"Sallèles-Cabardès", +"Sallèles-d'Aude", "Salm-Salm", +"Salm-en-Vosges", "Salon-de-Provence", "Salon-la-Tour", "Salornay-sur-Guye", -"salpingo-pharyngien", "Salses-le-Château", "Salt-en-Donzy", "Salvagnac-Cajarc", "Salvatierra-Agurain", -"salve-d'honneur", -"salves-d'honneur", "Samois-sur-Seine", -"(S)-amphétamine", "Sampigny-lès-Maranges", "Samsons-Lion", -"sam'suffit", -"sam'suffits", -"Sana'a", -"Sanary-sur-Mer", -"san-benito", -"san-bérinois", "San-Bérinois", -"san-bérinoise", "San-Bérinoise", -"san-bérinoises", "San-Bérinoises", -"Sancey-le-Grand", -"Sancey-le-Long", -"san-claudien", "San-Claudien", "San-Crucien", -"Sancti-Spíritus", -"sancto-bénédictin", -"Sancto-Bénédictin", -"sancto-bénédictine", -"Sancto-Bénédictine", -"sancto-bénédictines", -"Sancto-Bénédictines", -"sancto-bénédictins", -"Sancto-Bénédictins", -"sancto-julianais", -"Sancto-Julianais", -"sancto-julianaise", -"Sancto-Julianaise", -"sancto-julianaises", -"Sancto-Julianaises", -"sancto-prixin", -"Sancto-Prixin", -"sancto-prixine", -"Sancto-Prixine", -"sancto-prixines", -"Sancto-Prixines", -"sancto-prixins", -"Sancto-Prixins", -"Sancy-les-Cheminots", -"Sancy-lès-Provins", -"san-damianais", "San-Damianais", -"san-damianaise", "San-Damianaise", -"san-damianaises", "San-Damianaises", "San-Damiano", -"san-denien", "San-Denien", -"san-denienne", "San-Denienne", -"san-deniennes", "San-Deniennes", -"san-deniens", "San-Deniens", -"Sandersdorf-Brehna", -"san-desiderois", "San-Desiderois", -"san-desideroise", "San-Desideroise", -"san-desideroises", "San-Desideroises", -"san-farcios", "San-Farcios", -"san-farciose", "San-Farciose", -"san-farcioses", "San-Farcioses", -"san-ferrois", "San-Ferrois", -"san-ferroise", "San-Ferroise", -"san-ferroises", "San-Ferroises", "San-Gavino-d'Ampugnani", "San-Gavino-di-Carbini", "San-Gavino-di-Fiumorbo", "San-Gavino-di-Tenda", -"sang-de-bourbe", -"sang-de-dragon", -"san-genestois", "San-Genestois", -"san-genestoise", "San-Genestoise", -"san-genestoises", "San-Genestoises", -"san-germinois", "San-Germinois", -"san-germinoise", "San-Germinoise", -"san-germinoises", "San-Germinoises", -"sang-froid", -"sang-gris", "San-Giovanni-di-Moriani", "San-Giuliano", -"sang-mêlé", -"Sang-mêlé", -"sang-mêlés", -"Sang-mêlés", -"Sanilhac-Sagriès", -"sankaku-jime", -"san-lagiron", "San-Lagiron", -"san-lagirone", "San-Lagirone", -"san-lagirones", "San-Lagirones", -"san-lagirons", "San-Lagirons", "San-Lorenzo", "San-Martino-di-Lota", -"san-martinois", "San-Martinois", -"san-martinoise", "San-Martinoise", -"san-martinoises", "San-Martinoises", -"san-miardère", "San-Miardère", -"san-miardères", "San-Miardères", "San-Nicolao", -"san-palous", "San-Palous", -"san-palouse", "San-Palouse", -"san-palouses", "San-Palouses", -"san-pétri-montin", -"San-Pétri-Montin", -"san-pétri-montine", -"San-Pétri-Montine", -"san-pétri-montines", -"San-Pétri-Montines", -"san-pétri-montins", -"San-Pétri-Montins", -"san-pierran", "San-Pierran", -"san-pierrane", "San-Pierrane", -"san-pierranes", "San-Pierranes", -"san-pierrans", "San-Pierrans", "San-Priode", -"san-priot", "San-Priot", -"san-priote", -"san-priotes", "San-Priotes", -"san-priots", "San-Priots", -"san-rémois", +"San-Pétri-Montin", +"San-Pétri-Montine", +"San-Pétri-Montines", +"San-Pétri-Montins", "San-Rémois", -"san-rémoise", "San-Rémoise", -"san-rémoises", "San-Rémoises", +"San-Salvatorien", +"San-Salvatorienne", +"San-Salvatoriennes", +"San-Salvatoriens", +"San-Vitournaire", +"San-Vitournaires", +"Sana'a", +"Sanary-sur-Mer", +"Sancey-le-Grand", +"Sancey-le-Long", +"Sancti-Spíritus", +"Sancto-Bénédictin", +"Sancto-Bénédictine", +"Sancto-Bénédictines", +"Sancto-Bénédictins", +"Sancto-Julianais", +"Sancto-Julianaise", +"Sancto-Julianaises", +"Sancto-Prixin", +"Sancto-Prixine", +"Sancto-Prixines", +"Sancto-Prixins", +"Sancy-les-Cheminots", +"Sancy-lès-Provins", +"Sandersdorf-Brehna", +"Sang-mêlé", +"Sang-mêlés", +"Sanilhac-Sagriès", "Sanry-lès-Vigy", "Sanry-sur-Nied", -"Sansac-de-Marmiesse", +"Sans-Vallois", "Sansac-Veinazès", -"san-salvatorien", -"San-Salvatorien", -"san-salvatorienne", -"San-Salvatorienne", -"san-salvatoriennes", -"San-Salvatoriennes", -"san-salvatoriens", -"San-Salvatoriens", +"Sansac-de-Marmiesse", "Sanssac-l'Eglise", "Sanssac-l'Église", "Sant'Agapito", "Sant'Agnello", "Sant'Agostino", "Sant'Alfio", -"Santa-Lucia-di-Mercurio", -"Santa-Lucia-di-Moriani", -"Santa-Maria-di-Lota", -"Santa-Maria-Figaniella", -"Santa-Maria-Poggio", -"Santa-Maria-Siché", "Sant'Anastasia", +"Sant'Andréa-d'Orcino", "Sant'Andréa-di-Bozio", "Sant'Andréa-di-Cotone", "Sant'Andréa-di-Tallano", -"Sant'Andréa-d'Orcino", "Sant'Antimo", "Sant'Antioco", "Sant'Antonino", "Sant'Antonio", "Sant'Apollinare", "Sant'Arcangelo", -"Santa-Reparata-di-Balagna", -"Santa-Reparata-di-Moriani", "Sant'Arpino", "Sant'Arsenio", "Sant'Elena", -"Santiago-Pontones", -"santi-johanien", -"Santi-Johanien", -"santi-johanienne", -"Santi-Johanienne", -"santi-johaniennes", -"Santi-Johaniennes", -"santi-johaniens", -"Santi-Johaniens", "Sant'Ippolito", "Sant'Olcese", -"santoline-cyprès", "Sant'Omero", "Sant'Onofrio", +"Sant'Oreste", +"Sant'Urbano", +"Santa-Lucia-di-Mercurio", +"Santa-Lucia-di-Moriani", +"Santa-Maria-Figaniella", +"Santa-Maria-Poggio", +"Santa-Maria-Siché", +"Santa-Maria-di-Lota", +"Santa-Reparata-di-Balagna", +"Santa-Reparata-di-Moriani", +"Santi-Johanien", +"Santi-Johanienne", +"Santi-Johaniennes", +"Santi-Johaniens", +"Santiago-Pontones", "Santo-Pietro-di-Tenda", "Santo-Pietro-di-Venaco", -"Sant'Oreste", "Santpoort-Noord", "Santpoort-Zuid", -"Sant'Urbano", "Sanvignes-les-Mines", -"san-vitournaire", -"San-Vitournaire", -"san-vitournaires", -"San-Vitournaires", -"Saône-et-Loire", "Sap-en-Auge", -"sapeur-pompier", -"sapeurs-pompiers", -"sapeuse-pompière", -"sapeuses-pompières", -"Sapogne-et-Feuchères", "Sapogne-Feuchères", +"Sapogne-et-Feuchères", "Sapogne-sur-Marche", -"sarclo-buttage", -"sarclo-buttages", -"sarco-épiplocèle", -"sarco-épiplomphale", -"sarco-épiplomphales", -"sarco-hydrocèle", -"sarco-hydrocèles", "Sardy-lès-Epiry", "Sardy-lès-Épiry", "Sargé-lès-le-Mans", "Sargé-sur-Braye", -"Sariac-Magnoac", -"Sari-di-Porto-Vecchio", -"Sari-d'Orcino", "Sari-Solenzara", +"Sari-d'Orcino", +"Sari-di-Porto-Vecchio", +"Sariac-Magnoac", "Sarlat-la-Canéda", "Sarliac-sur-l'Isle", "Saron-sur-Aube", "Sarre-Palatinat", "Sarre-Union", -"sarre-unionnais", "Sarre-Unionnais", -"sarre-unionnaise", "Sarre-Unionnaise", -"sarre-unionnaises", "Sarre-Unionnaises", "Sarriac-Bigorre", "Sarrola-Carcopino", "Sarroux-Saint-Julien", +"Sars-Poteries", "Sars-et-Rosières", "Sars-la-Bruyère", "Sars-la-Buissière", "Sars-le-Bois", -"Sars-Poteries", "Sart-Bernard", "Sart-Custinne", "Sart-Dames-Avelines", -"sart-dames-avelinois", "Sart-Dames-Avelinois", "Sart-Dames-Avelinoise", -"Sart-en-Fagne", "Sart-Eustache", -"sart-eustachois", "Sart-Eustachois", "Sart-Eustachoise", -"Sartilly-Baie-Bocage", "Sart-Messire-Guillaume", "Sart-Risbart", -"sart-risbartois", "Sart-Risbartois", "Sart-Risbartoise", "Sart-Saint-Laurent", +"Sart-en-Fagne", +"Sartilly-Baie-Bocage", "Sas-de-Gand", "Sassen-Trantow", "Sassetot-le-Malgardé", "Sassetot-le-Mauconduit", "Sassey-sur-Meuse", "Sassierges-Saint-Germain", -"satellites-espions", "Sathonay-Camp", "Sathonay-Village", -"sati-drap", "Satolas-et-Bonce", "Sauchy-Cauchy", "Sauchy-Lestrée", "Saucourt-sur-Rognon", -"sauf-conduit", -"sauf-conduits", "Saugnac-et-Cambran", -"saugnac-et-muretois", "Saugnac-et-Muretois", -"saugnac-et-muretoise", "Saugnac-et-Muretoise", -"saugnac-et-muretoises", "Saugnac-et-Muretoises", "Saugnacq-et-Muret", "Sauguis-Saint-Etienne", "Sauguis-Saint-Étienne", -"Saulces-aux-Bois", +"Saulce-sur-Rhône", "Saulces-Champenoises", "Saulces-Monclin", -"Saulce-sur-Rhône", "Saulces-Vieille", +"Saulces-aux-Bois", "Saulchoy-sous-Poix", "Saulchoy-sur-Davenescourt", "Saulcy-sur-Meurthe", @@ -22121,20 +15413,17 @@ FR_BASE_EXCEPTIONS = [ "Saulon-la-Chapelle", "Saulon-la-Rue", "Sault-Brénaz", -"Saultchevreuil-du-Tronchet", -"Sault-de-Navailles", -"Sault-lès-Rethel", -"sault-rethelois", "Sault-Rethelois", -"sault-retheloise", "Sault-Retheloise", -"sault-retheloises", "Sault-Retheloises", "Sault-Saint-Remy", -"Saulx-le-Duc", -"Saulx-lès-Champlon", -"Saulx-les-Chartreux", +"Sault-de-Navailles", +"Sault-lès-Rethel", +"Saultchevreuil-du-Tronchet", "Saulx-Marchais", +"Saulx-le-Duc", +"Saulx-les-Chartreux", +"Saulx-lès-Champlon", "Saulxures-lès-Bulgnéville", "Saulxures-lès-Nancy", "Saulxures-lès-Vannes", @@ -22148,33 +15437,14 @@ FR_BASE_EXCEPTIONS = [ "Saussay-la-Campagne", "Sausset-les-Pins", "Sausseuzemare-en-Caux", -"saut-de-lit", -"saut-de-lits", -"saut-de-loup", -"saut-de-mouton", -"saute-au-paf", -"saute-bouchon", -"saute-bouchons", -"saute-en-barque", -"saute-en-bas", -"saute-mouton", -"saute-moutons", -"saute-ruisseau", -"saute-ruisseaux", -"sauts-de-lit", -"sauts-de-mouton", "Sauvage-Magny", "Sauvagnat-Sainte-Marthe", -"sauve-l'honneur", -"sauve-qui-peut", -"sauve-rabans", +"Sauveterre-Saint-Denis", "Sauveterre-de-Béarn", "Sauveterre-de-Comminges", "Sauveterre-de-Guyenne", "Sauveterre-de-Rouergue", "Sauveterre-la-Lémance", -"Sauveterre-Saint-Denis", -"sauve-vie", "Sauviat-sur-Vige", "Sauvigney-lès-Gray", "Sauvigney-lès-Pesmes", @@ -22185,32 +15455,27 @@ FR_BASE_EXCEPTIONS = [ "Saux-et-Pomarède", "Sauzé-Vaussais", "Savas-Mépin", -"savez-vous", +"Savignac-Lédrier", +"Savignac-Mona", "Savignac-de-Duras", -"Savignac-de-l'Isle", "Savignac-de-Miremont", "Savignac-de-Nontron", -"Savignac-Lédrier", +"Savignac-de-l'Isle", "Savignac-les-Eglises", -"Savignac-les-Églises", "Savignac-les-Ormeaux", -"Savignac-Mona", +"Savignac-les-Églises", "Savignac-sur-Leyze", -"Savigné-l'Evêque", -"Savigné-l'Évêque", -"Savigné-sous-le-Lude", -"Savigné-sur-Lathan", +"Savigny-Lévescault", +"Savigny-Poil-Fol", "Savigny-en-Revermont", "Savigny-en-Sancerre", "Savigny-en-Septaine", "Savigny-en-Terre-Plaine", "Savigny-en-Véron", -"Savigny-lès-Beaune", "Savigny-le-Sec", "Savigny-le-Temple", -"Savigny-Lévescault", "Savigny-le-Vieux", -"Savigny-Poil-Fol", +"Savigny-lès-Beaune", "Savigny-sous-Faye", "Savigny-sous-Mâlain", "Savigny-sur-Aisne", @@ -22220,9 +15485,11 @@ FR_BASE_EXCEPTIONS = [ "Savigny-sur-Grosne", "Savigny-sur-Orge", "Savigny-sur-Seille", +"Savigné-l'Evêque", +"Savigné-l'Évêque", +"Savigné-sous-le-Lude", +"Savigné-sur-Lathan", "Savines-le-Lac", -"savoir-faire", -"savoir-vivre", "Savonnières-devant-Bar", "Savonnières-en-Perthois", "Savonnières-en-Woëvre", @@ -22231,35 +15498,25 @@ FR_BASE_EXCEPTIONS = [ "Saxe-du-Nord", "Saxi-Bourdon", "Saxon-Sion", -"scale-out", -"scale-up", -"scaphoïdo-astragalien", -"scaphoïdo-cuboïdien", -"sceau-cylindre", -"sceau-de-Notre-Dame", -"sceau-de-salomon", +"Saâcy-sur-Marne", +"Saâne-Saint-Just", +"Saône-et-Loire", "Sceau-Saint-Angel", -"sceaux-cylindres", "Sceaux-d'Anjou", -"sceaux-de-Notre-Dame", "Sceaux-du-Gâtinais", "Sceaux-sur-Huisne", -"scènes-clés", "Scey-Maisières", "Scey-sur-Saône", "Scey-sur-Saône-et-Saint-Albin", "Schacht-Audorf", "Schaffhouse-près-Seltz", "Schaffhouse-sur-Zorn", -"S-chanf", "Scharrachbergheim-Irmstett", "Scheibe-Alsbach", "Schieder-Schwalenberg", "Schinznach-Bad", "Schiphol-Oost", "Schiphol-Rijk", -"schiste-carton", -"schistes-carton", "Schlatt-Haslen", "Schleswig-Flensburg", "Schleswig-Holstein", @@ -22267,38 +15524,18 @@ FR_BASE_EXCEPTIONS = [ "Schmogrow-Fehrow", "Schmölln-Putzkau", "Schnarup-Thumby", -"Schönau-Berzdorf", -"Schönenberg-Kübelberg", -"Schönwalde-Glien", "Schouwen-Duiveland", "Schwalm-Eder", "Schweigen-Rechtenbach", -"Schweighouse-sur-Moder", "Schweighouse-Thann", -"scie-cloche", -"science-fictif", -"science-fiction", -"science-fictions", -"sciences-fiction", -"sciences-fictions", -"scies-cloches", +"Schweighouse-sur-Moder", +"Schönau-Berzdorf", +"Schönenberg-Kübelberg", +"Schönwalde-Glien", "Scieurac-et-Flourès", -"scirpo-phragmitaie", -"scirpo-phragmitaies", "Scorbé-Clairvaux", -"scottish-terrier", -"scuto-sternal", "Scy-Chazelles", -"S.-E.", "Sealyham-terrier", -"Sébazac-Concourès", -"sèche-cheveu", -"sèche-cheveux", -"sèche-linge", -"séchoir-atomiseur", -"séchoir-atomiseurs", -"seconde-lumière", -"secondes-lumière", "Secondigné-sur-Belle", "Secqueville-en-Bessin", "Sedze-Maubecq", @@ -22306,464 +15543,174 @@ FR_BASE_EXCEPTIONS = [ "Seeheim-Jugenheim", "Seeon-Seebruck", "Seeth-Ekholt", -"Séez-Mesnil", -"Ségrie-Fontaine", -"Ségur-le-Château", -"Ségur-les-Villas", "Seiches-sur-le-Loir", "Seillons-Source-d'Argens", -"seine-et-marnais", -"Seine-et-Marnais", -"seine-et-marnaise", -"Seine-et-Marnaise", -"seine-et-marnaises", -"Seine-et-Marnaises", -"Seine-et-Marne", -"Seine-et-Oise", "Seine-Inférieure", "Seine-Maritime", "Seine-Port", -"seine-portais", "Seine-Portais", -"seine-portaise", "Seine-Portaise", -"seine-portaises", "Seine-Portaises", "Seine-Saint-Denis", +"Seine-et-Marnais", +"Seine-et-Marnaise", +"Seine-et-Marnaises", +"Seine-et-Marne", +"Seine-et-Oise", "Seitingen-Oberflacht", -"self-control", -"self-défense", -"self-government", -"self-governments", -"self-made-man", -"self-made-mans", -"self-made-men", -"self-made-woman", -"self-made-womans", -"self-made-women", -"self-service", -"self-services", -"Selke-Aue", -"selk'nam", "Selk'nam", +"Selke-Aue", "Selles-Saint-Denis", -"selles-sur-cher", "Selles-sur-Cher", "Selles-sur-Nahon", "Selon-Jean", "Selon-Luc", "Selon-Marc", "Selon-Matthieu", -"semaine-lumière", -"semaines-lumière", -"Séméacq-Blachon", -"semen-contra", -"Sémézies-Cachan", "Semoutiers-Montsaon", -"semper-virens", "Semur-en-Auxois", "Semur-en-Brionnais", "Semur-en-Vallon", -"Sénaillac-Latronquière", -"Sénaillac-Lauzès", "Senargent-Mignafans", -"sénateur-maire", -"sénatus-consulte", -"sénatus-consultes", "Sencenac-Puy-de-Fourches", "Senesse-de-Senabugue", "Senillé-Saint-Sauveur", "Senlis-le-Sec", -"Sennecé-lès-Mâcon", "Sennecey-le-Grand", "Sennecey-lès-Dijon", +"Sennecé-lès-Mâcon", "Senneville-sur-Fécamp", "Sennevoy-le-Bas", "Sennevoy-le-Haut", "Senoncourt-les-Maujouy", "Sens-Beaujeu", "Sens-de-Bretagne", -"sensori-moteur", -"sensori-moteurs", -"sensori-motrice", -"sensori-motrices", -"sensori-motricité", "Sens-sur-Seille", -"sent-bon", -"Sentenac-de-Sérou", "Sentenac-d'Oust", +"Sentenac-de-Sérou", "Senven-Léhart", "Seo-yeon", "Seppois-le-Bas", "Seppois-le-Haut", -"septante-cinq", -"septante-deux", -"septante-et-un", -"septante-huit", -"septante-neuf", -"septante-quatre", -"septante-sept", -"septante-six", -"septante-trois", -"Septèmes-les-Vallons", -"sept-en-gueule", -"sept-en-huit", -"septentrio-occidental", -"septentrio-occidentale", -"septentrio-occidentales", -"septentrio-occidentaux", -"sept-et-le-va", "Sept-Forges", "Sept-Frères", -"sept-frèrien", "Sept-Frèrien", -"sept-frèrienne", "Sept-Frèrienne", -"sept-frèriennes", "Sept-Frèriennes", -"sept-frèriens", "Sept-Frèriens", -"Sept-Îles", "Sept-Ilien", -"Sept-Îlien", -"Sept-Îlois", "Sept-Insulaire", "Sept-Insulaires", "Sept-Lacquois", -"sept-mâts", "Sept-Meules", -"sept-meulois", "Sept-Meulois", -"sept-meuloise", "Sept-Meuloise", -"sept-meuloises", "Sept-Meuloises", -"sept-oeil", -"sept-œil", -"sept-oeils", -"sept-œils", "Sept-Saulx", -"sept-sortais", "Sept-Sortais", -"sept-sortaise", "Sept-Sortaise", -"sept-sortaises", "Sept-Sortaises", "Sept-Sorts", -"sept-ventais", "Sept-Ventais", -"sept-ventaise", "Sept-Ventaise", -"sept-ventaises", "Sept-Ventaises", "Sept-Vents", +"Sept-Îles", +"Sept-Îlien", +"Sept-Îlois", +"Septèmes-les-Vallons", "Sepulcro-Hilario", -"Séquano-Dionysien", "Seraing-le-Château", -"Séranvillers-Forenville", "Seraucourt-le-Grand", "Serbie-et-Monténégro", -"serbo-croate", -"Sère-en-Lavedan", -"Sère-Lanso", -"Serémange-Erzange", -"Sère-Rustaing", -"Sérézin-de-la-Tour", -"Sérézin-du-Rhône", -"sergent-chef", -"sergent-major", -"sergents-chefs", -"sergents-majors", -"Sérignac-Péboudou", -"Sérignac-sur-Garonne", -"Sérignan-du-Comtat", "Seringes-et-Nesles", "Sermaize-les-Bains", "Sermoise-sur-Loire", -"séro-sanguin", -"séro-sanguine", -"séro-sanguines", -"séro-sanguins", "Serra-di-Ferro", "Serra-di-Fiumorbo", "Serra-di-Scopamène", -"serre-bauquière", -"serre-bosse", -"serre-bosses", -"serre-bras", -"serre-ciseau", -"serre-ciseaux", -"serre-cou", -"serre-cous", -"serre-feu", -"serre-feux", -"serre-fil", -"serre-file", -"serre-files", -"serre-fils", -"serre-fine", -"serre-frein", -"serre-joint", -"serre-joints", +"Serre-Nerpol", +"Serre-Nerpolain", +"Serre-Nerpolaine", +"Serre-Nerpolaines", +"Serre-Nerpolains", "Serre-les-Moulières", "Serre-les-Sapins", -"serre-livre", -"serre-livres", -"serre-malice", -"Serre-Nerpol", -"serre-nerpolain", -"Serre-Nerpolain", -"serre-nerpolaine", -"Serre-Nerpolaine", -"serre-nerpolaines", -"Serre-Nerpolaines", -"serre-nerpolains", -"Serre-Nerpolains", -"serre-nez", -"serre-noeud", -"serre-nœud", -"serre-nœuds", -"serre-papier", -"serre-papiers", -"serre-pédicule", -"serre-pédicules", -"serre-point", -"serre-points", -"serre-rails", "Serres-Castet", -"Serres-et-Montguyard", -"serres-fines", "Serres-Gaston", -"serres-gastonnais", "Serres-Gastonnais", -"serres-gastonnaise", "Serres-Gastonnaise", -"serres-gastonnaises", "Serres-Gastonnaises", -"Serreslous-et-Arribans", -"Serres-Morlaàs", -"serres-morlanais", "Serres-Morlanais", -"serres-morlanaise", "Serres-Morlanaise", -"serres-morlanaises", "Serres-Morlanaises", +"Serres-Morlaàs", "Serres-Sainte-Marie", +"Serres-et-Montguyard", "Serres-sur-Arget", -"serre-taille", -"serre-tailles", -"serre-tête", -"serre-têtes", -"serre-tube", -"serre-tubes", +"Serreslous-et-Arribans", +"Serri-Sapinois", +"Serri-Sapinoise", +"Serri-Sapinoises", +"Serrigny-en-Bresse", "Serrières-de-Briord", "Serrières-en-Chautagne", "Serrières-sur-Ain", -"Serrigny-en-Bresse", -"serri-sapinois", -"Serri-Sapinois", -"serri-sapinoise", -"Serri-Sapinoise", -"serri-sapinoises", -"Serri-Sapinoises", "Servance-Miellin", "Servaville-Salmonville", "Serves-sur-Rhône", -"services-volées", -"service-volée", -"Servières-le-Château", "Serviers-et-Labaume", -"Serviès-en-Val", -"serviette-éponge", -"serviettes-éponges", "Servigny-lès-Raville", "Servigny-lès-Sainte-Barbe", -"servo-direction", -"servo-directions", -"servo-frein", -"servo-freins", -"servo-moteur", +"Servières-le-Château", +"Serviès-en-Val", "Servon-Melzicourt", "Servon-sur-Vilaine", -"Séry-lès-Mézières", -"Séry-Magneval", "Serzy-et-Prin", +"Serémange-Erzange", "Seuil-d'Argonne", -"seule-en-scène", -"seul-en-scène", -"Sévérac-d'Aveyron", -"Sévérac-le-Château", -"Sévérac-l'Eglise", -"Sévérac-l'Église", -"Sévignacq-Meyracq", -"Sévignacq-Thèze", -"Sévigny-la-Forêt", -"Sévigny-Waleppe", -"Sèvres-Anxaumont", -"sex-appeal", -"sex-digital", -"sex-digitisme", -"sex-digitismes", -"sexe-ratio", "Sexey-aux-Forges", "Sexey-les-Bois", -"sex-ratio", -"sex-ratios", -"sex-shop", -"sex-shops", -"sex-symbol", -"sex-symbols", -"sex-toy", -"sex-toys", "Seysses-Savès", "Seyssinet-Pariset", -"shabu-shabu", "Shai-hulud", "Shang-Haï", -"shar-peï", -"shar-peïs", -"shift-cliqua", -"shift-cliquai", -"shift-cliquaient", -"shift-cliquais", -"shift-cliquait", -"shift-cliquâmes", -"shift-cliquant", -"shift-cliquas", -"shift-cliquasse", -"shift-cliquassent", -"shift-cliquasses", -"shift-cliquassiez", -"shift-cliquassions", -"shift-cliquât", -"shift-cliquâtes", -"shift-clique", -"shift-cliqué", -"shift-cliquée", -"shift-cliquées", -"shift-cliquent", -"shift-cliquer", -"shift-cliquera", -"shift-cliquerai", -"shift-cliqueraient", -"shift-cliquerais", -"shift-cliquerait", -"shift-cliqueras", -"shift-cliquèrent", -"shift-cliquerez", -"shift-cliqueriez", -"shift-cliquerions", -"shift-cliquerons", -"shift-cliqueront", -"shift-cliques", -"shift-cliqués", -"shift-cliquez", -"shift-cliquiez", -"shift-cliquions", -"shift-cliquons", -"shikoku-inu", -"shipibo-conibo", -"shoot-'em-up", "Shoreham-by-Sea", -"short-culotte", -"short-culottes", -"short-track", -"short-tracks", -"show-biz", -"show-business", "Siaugues-Sainte-Marie", "Siccieu-Saint-Julien-et-Carisieu", -"sicilio-sarde", -"side-car", -"side-cariste", -"side-caristes", -"side-cars", -"siècle-lumière", -"siècles-lumière", "Siegen-Wittgenstein", "Sierck-les-Bains", -"sierra-léonais", "Sierra-Léonais", -"sierra-léonaise", "Sierra-Léonaise", -"sierra-léonaises", "Sierra-Léonaises", "Sierra-Léonien", "Sieversdorf-Hohenofen", -"sigma-additif", -"sigma-additivité", -"sigma-additivités", "Signy-Avenex", -"Signy-l'Abbaye", -"Signy-le-Grand", -"Signy-le-Petit", "Signy-Librecy", "Signy-Montlibert", "Signy-Signets", +"Signy-l'Abbaye", +"Signy-le-Grand", +"Signy-le-Petit", "Sigy-en-Bray", "Sigy-le-Châtel", -"silicico-aluminique", -"silicico-aluminiques", -"silicico-cuivreux", "Sillans-la-Cascade", -"Sillé-le-Guillaume", -"Sillé-le-Philippe", "Silley-Amancey", "Silley-Bléfond", +"Silly-Tillard", "Silly-en-Gouffern", "Silly-en-Saulnois", "Silly-la-Poterie", "Silly-le-Long", "Silly-sur-Nied", -"Silly-Tillard", -"silure-spatule", +"Sillé-le-Guillaume", +"Sillé-le-Philippe", "Simandre-sur-Suran", "Simiane-Collongue", "Simiane-la-Rotonde", -"simili-cuir", -"simili-cuirs", "Simon-la-Vineuse", -"Sincey-lès-Rouvray", -"singe-araignée", -"singe-chouette", -"singe-écureuil", -"singe-lion", -"singes-araignées", -"singes-chouettes", -"singes-écureuils", -"singes-lions", "Sin-le-Noble", -"sino-américain", -"sino-américaine", -"sino-américaines", -"sino-américains", -"sino-australien", -"sino-australienne", -"sino-australiennes", -"sino-australiens", -"sino-canadien", -"sino-colombien", -"sino-colombienne", -"sino-colombiennes", -"sino-colombiens", -"sino-congolais", -"sino-continental", -"sino-coréen", -"sino-égyptien", -"sino-égyptienne", -"sino-égyptiennes", -"sino-égyptiens", -"sino-européen", -"sino-japonais", -"sino-japonaise", -"sino-japonaises", -"sino-québécois", -"sino-taïwanais", -"sino-tibétain", -"sino-vietnamien", -"sino-vietnamienne", -"sino-vietnamiennes", -"sino-vietnamiens", +"Sincey-lès-Rouvray", "Sint-Amands", "Sint-Andries", "Sint-Annaland", @@ -22815,107 +15762,32 @@ FR_BASE_EXCEPTIONS = [ "Siorac-de-Ribérac", "Siorac-en-Périgord", "Siouville-Hague", -"sister-ship", -"sister-ships", -"sit-in", -"sit-ins", "Sittard-Geleen", -"sit-up", -"sit-ups", "Sivry-Ante", "Sivry-Courtry", +"Sivry-Rance", "Sivry-la-Perche", "Sivry-lès-Buzancy", -"Sivry-Rance", "Sivry-sur-Meuse", -"six-cents", -"six-cent-soixante-six", -"six-cent-soixante-sixième", -"six-cent-soixante-sixièmes", -"six-clefs", -"six-coups", -"six-doigts", -"six-fournais", "Six-Fournais", -"six-fournaise", "Six-Fournaise", -"six-fournaises", "Six-Fournaises", "Six-Fours-la-Plage", "Six-Fours-les-Plages", -"six-mâts", "Six-Planes", "Sixt-Fer-à-Cheval", "Sixt-sur-Aff", -"six-vingts", "Skelton-in-Cleveland", -"ski-alpinisme", -"ski-alpinismes", -"ski-alpiniste", -"ski-alpinistes", "Skye-terrier", -"sleeping-car", "Slijk-Ewijk", -"sloop-of-war", -"slop-tank", "Sluis-Aardenburg", -"smaragdo-chalcite", -"smaragdo-chalcites", "Smeerebbe-Vloerzegem", -"S-métolachlore", -"snack-bar", -"snack-bars", "Snijders-Chaam", -"snow-boot", -"snow-boots", -"soap-opéra", -"soaps-opéras", -"sociale-démocrate", -"sociales-démocrates", -"sociales-traitres", -"sociales-traîtres", -"sociale-traitre", -"sociale-traître", -"sociaux-démocrates", -"sociaux-traitres", -"sociaux-traîtres", -"société-écran", -"sociétés-écrans", -"socio-cible", -"socio-cibles", -"socio-culturel", -"socio-culturelle", -"socio-culturelles", -"socio-culturels", -"socio-économique", -"socio-économiques", -"socio-éducatif", -"socio-éducatifs", -"socio-éducative", -"socio-éducatives", -"socio-esthéticien", -"socio-esthéticiens", -"socio-historiographe", -"socio-historiographes", -"socio-historique", -"socio-historiques", -"socio-politique", -"socio-politiques", -"socio-professionnel", -"socio-professionnelle", -"socio-professionnelles", -"socio-professionnels", -"soda-spodumenes", -"sodo-calcique", -"sodo-calciques", "Sognolles-en-Montois", "Sogny-aux-Moulins", "Sogny-en-l'Angle", "Soheit-Tinlot", -"soi-disamment", -"soi-disant", "Soignolles-en-Brie", -"soi-même", "Soing-Cubry-Charentenay", "Soings-en-Sologne", "Soirans-Fouffrans", @@ -22923,40 +15795,10 @@ FR_BASE_EXCEPTIONS = [ "Soisy-Bouy", "Soisy-sous-Montmorency", "Soisy-sur-Ecole", -"Soisy-sur-École", "Soisy-sur-Seine", -"soit-communiqué", -"soixante-cinq", -"soixante-deux", -"soixante-dix", -"soixante-dix-huit", -"soixante-dixième", -"soixante-dixièmes", -"soixante-dix-neuf", -"soixante-dix-sept", -"soixante-dizaine", -"soixante-dizaines", -"soixante-douze", -"soixante-et-onze", -"soixante-et-un", -"soixante-et-une", -"soixante-huit", -"soixante-huitard", -"soixante-huitarde", -"soixante-huitardes", -"soixante-huitards", -"soixante-neuf", -"soixante-quatorze", -"soixante-quatre", -"soixante-quinze", -"soixante-seize", -"soixante-sept", -"soixante-six", -"soixante-treize", -"soixante-trois", +"Soisy-sur-École", "Soizy-aux-Bois", "Solaure-en-Diois", -"sole-ruardon", "Solignac-sous-Roche", "Solignac-sur-Loire", "Soligny-la-Trappe", @@ -22964,58 +15806,38 @@ FR_BASE_EXCEPTIONS = [ "Soligny-les-Étangs", "Sollières-Sardières", "Solliès-Pont", -"solliès-pontois", "Solliès-Pontois", -"solliès-pontoise", "Solliès-Pontoise", -"solliès-pontoises", "Solliès-Pontoises", "Solliès-Toucas", -"solliès-villain", "Solliès-Villain", -"solliès-villaine", "Solliès-Villaine", -"solliès-villaines", "Solliès-Villaines", -"solliès-villains", "Solliès-Villains", "Solliès-Ville", -"Solre-le-Château", "Solre-Saint-Géry", +"Solre-le-Château", "Solre-sur-Sambre", "Soltau-Fallingbostel", "Solutré-Pouilly", -"somato-psychique", -"somato-psychiques", "Someren-Eind", "Someren-Heide", "Somme-Bionne", "Somme-Leuze", -"somme-leuzien", "Somme-Leuzien", "Somme-Leuzienne", -"Sommepy-Tahure", -"somme-suippas", "Somme-Suippas", -"somme-suippase", "Somme-Suippase", -"somme-suippases", "Somme-Suippases", "Somme-Suippe", "Somme-Tourbe", -"Sommette-Eaucourt", "Somme-Vesle", "Somme-Yèvre", +"Sommepy-Tahure", +"Sommette-Eaucourt", "Sommières-du-Clain", "Sonceboz-Sombeval", "Soncourt-sur-Marne", -"son-et-lumière", -"soŋay-zarma", -"soŋay-zarmas", -"songe-creux", -"songe-malice", -"songhaï-zarma", -"songhaï-zarmas", "Sonnac-sur-l'Hers", "Sonnenberg-Winnenberg", "Sons-et-Ronchères", @@ -23029,76 +15851,26 @@ FR_BASE_EXCEPTIONS = [ "Sorcy-Bauthémont", "Sorcy-Saint-Martin", "Sorde-l'Abbaye", -"Sorel-en-Vimeu", "Sorel-Moussel", +"Sorel-en-Vimeu", "Sorinne-la-Longue", "Sornzig-Ablaß", "Sort-en-Chalosse", -"sortie-de-bain", -"sortie-de-bal", "Sortosville-en-Beaumont", -"sot-l'y-laisse", "Sotteville-lès-Rouen", "Sotteville-sous-le-Val", "Sotteville-sur-Mer", -"sotto-voce", "Souain-Perthes-lès-Hurlus", "Souancé-au-Perche", -"sou-chong", -"sou-chongs", "Soucieu-en-Jarrest", "Soudaine-Lavinadière", -"soudano-tchado-lybien", "Soudé-Notre-Dame-ou-le-Petit", -"soudo-brasa", -"soudo-brasai", -"soudo-brasaient", -"soudo-brasais", -"soudo-brasait", -"soudo-brasâmes", -"soudo-brasant", -"soudo-brasas", -"soudo-brasasse", -"soudo-brasassent", -"soudo-brasasses", -"soudo-brasassiez", -"soudo-brasassions", -"soudo-brasât", -"soudo-brasâtes", -"soudo-brase", -"soudo-brasé", -"soudo-brasée", -"soudo-brasées", -"soudo-brasent", -"soudo-braser", -"soudo-brasera", -"soudo-braserai", -"soudo-braseraient", -"soudo-braserais", -"soudo-braserait", -"soudo-braseras", -"soudo-brasèrent", -"soudo-braserez", -"soudo-braseriez", -"soudo-braserions", -"soudo-braserons", -"soudo-braseront", -"soudo-brases", -"soudo-brasés", -"soudo-brasez", -"soudo-brasiez", -"soudo-brasions", -"soudo-brasons", "Soueix-Rogalle", -"souffre-douleur", -"souffre-douleurs", -"soufre-sélénifère", -"Sougé-le-Ganelon", -"Sougères-en-Puisaye", -"Sougères-sur-Sinotte", "Sougné-Remouchamps", "Sougy-sur-Loire", -"souï-manga", +"Sougères-en-Puisaye", +"Sougères-sur-Sinotte", +"Sougé-le-Ganelon", "Soulac-sur-Mer", "Soulages-Bonneval", "Soulaines-Dhuys", @@ -23112,491 +15884,176 @@ FR_BASE_EXCEPTIONS = [ "Souligné-sous-Ballon", "Soulosse-sous-Saint-Elophe", "Soulosse-sous-Saint-Élophe", -"Soultzbach-les-Bains", "Soultz-Haut-Rhin", "Soultz-les-Bains", "Soultz-sous-Forêts", +"Soultzbach-les-Bains", "Soumont-Saint-Quentin", -"soum-soum", -"soupe-tout-seul", "Souppes-sur-Loing", "Source-Seine", "Sourcieux-les-Mines", -"sourde-muette", -"sourdes-muettes", +"Sourdeval-Vengeons", "Sourdeval-la-Barre", "Sourdeval-les-Bois", -"Sourdeval-Vengeons", -"sourd-muet", -"sourd-parlant", -"sourds-muets", -"souris-chauve", -"souris-chauves", -"souris-crayon", -"souris-crayons", -"souris-opossums", -"souris-stylo", -"souris-stylos", +"Sous-Parsat", "Sousceyrac-en-Quercy", "Soussey-sur-Brionne", "Southend-on-Sea", -"soutien-gorge", -"soutien-loloches", -"soutiens-gorge", -"souvenez-vous-de-moi", -"souveraineté-association", -"Souvigné-sur-Même", -"Souvigné-sur-Sarthe", "Souvigny-de-Touraine", "Souvigny-en-Sologne", +"Souvigné-sur-Même", +"Souvigné-sur-Sarthe", "Souzay-Champigny", "Souzy-la-Briche", "Soye-en-Septaine", "Spaarndam-Oost", "Spaarndam-West", -"sparring-partner", -"spatio-temporel", -"spatio-temporelle", -"spatio-temporelles", -"spatio-temporels", "Spechbach-le-Bas", "Spechbach-le-Haut", -"speed-dating", -"sphéno-temporal", -"sphinx-bourdon", "Spider-Man", "Spiesen-Elversberg", -"spina-bifida", -"spina-ventosa", -"spin-off", -"spin-offs", -"spiro-bloc", -"spiro-blocs", -"sport-étude", -"sportivo-financier", -"sports-études", "Sprang-Capelle", "Spree-Neisse", -"spruce-beer", -"squale-grogneur", -"sri-lankais", "Sri-Lankais", -"sri-lankaise", "Sri-Lankaise", -"sri-lankaises", "Sri-Lankaises", -"stabilo-bossa", -"stabilo-bossai", -"stabilo-bossaient", -"stabilo-bossais", -"stabilo-bossait", -"stabilo-bossâmes", -"stabilo-bossant", -"stabilo-bossas", -"stabilo-bossasse", -"stabilo-bossassent", -"stabilo-bossasses", -"stabilo-bossassiez", -"stabilo-bossassions", -"stabilo-bossât", -"stabilo-bossâtes", -"stabilo-bosse", -"stabilo-bossé", -"stabilo-bossée", -"stabilo-bossées", -"stabilo-bossent", -"stabilo-bosser", -"stabilo-bossera", -"stabilo-bosserai", -"stabilo-bosseraient", -"stabilo-bosserais", -"stabilo-bosserait", -"stabilo-bosseras", -"stabilo-bossèrent", -"stabilo-bosserez", -"stabilo-bosseriez", -"stabilo-bosserions", -"stabilo-bosserons", -"stabilo-bosseront", -"stabilo-bosses", -"stabilo-bossés", -"stabilo-bossez", -"stabilo-bossiez", -"stabilo-bossions", -"stabilo-bossons", +"St-Jean", "Stadecken-Elsheim", "Stafordshire-bull-terrier", -"stage-coach", -"stage-coachs", "Staines-upon-Thames", -"stand-by", -"stand-up", "Stanford-le-Hope", -"stannoso-potassique", "Starrkirch-Wil", -"star-système", -"star-systèmes", -"starting-block", -"starting-blocks", -"starting-gate", -"start-up", -"start-upeur", -"st'at'imc", -"station-service", -"stations-service", -"stations-services", -"statue-menhir", -"statues-menhirs", "Staudach-Egerndach", -"steam-boat", -"steam-boats", "Stechow-Ferchesar", "Steenhuize-Wijnhuize", -"steeple-chase", "Steg-Hohtenn", -"Steinbach-Hallenberg", "Stein-Bockenheim", -"Steinbrunn-le-Bas", -"Steinbrunn-le-Haut", "Stein-Neukirch", "Stein-Wingert", +"Steinbach-Hallenberg", +"Steinbrunn-le-Bas", +"Steinbrunn-le-Haut", "Stelle-Wittenwurth", -"sténo-dactylographe", -"sténo-dactylographes", -"sténo-méditerranéen", -"sténo-méditerranéenne", -"sténo-méditerranéennes", -"sténo-méditerranéens", -"step-back", -"step-backs", -"stéphano-carladésien", -"Stéphano-Carladésien", -"stéphano-carladésienne", -"Stéphano-Carladésienne", -"stéphano-carladésiennes", -"Stéphano-Carladésiennes", -"stéphano-carladésiens", -"Stéphano-Carladésiens", -"stéréo-isomère", -"stéréo-isomères", -"sterno-claviculaire", -"sterno-claviculaires", -"sterno-cléido-mastoïdien", -"sterno-cléido-mastoïdiens", -"sterno-clido-mastoïdien", -"sterno-clido-mastoïdienne", -"sterno-clido-mastoïdiennes", -"sterno-clido-mastoïdiens", -"sterno-huméral", -"sterno-hyoïdien", -"sterno-pubien", "Stiring-Wendel", -"St-Jean", -"stock-car", -"stock-cars", "Stockhausen-Illfurth", -"stock-option", -"stock-options", -"stocks-tampons", -"stock-tampon", "Stockton-on-Tees", "Stockum-Püschen", "Stoke-on-Trent", -"stomo-gastrique", -"stomo-gastriques", -"stop-ski", -"stop-skis", "Storbeck-Frankendorf", -"story-board", -"story-boards", -"Straßlach-Dingharting", "Stratford-on-Avon", "Straubing-Bogen", -"strauss-kahnien", -"strauss-kahniens", -"street-artiste", -"street-artistes", -"street-gadz", -"Strépy-Bracquegnies", -"strip-teasa", -"strip-teasai", -"strip-teasaient", -"strip-teasais", -"strip-teasait", -"strip-teasâmes", -"strip-teasant", -"strip-teasas", -"strip-teasasse", -"strip-teasassent", -"strip-teasasses", -"strip-teasassiez", -"strip-teasassions", -"strip-teasât", -"strip-teasâtes", -"strip-tease", -"strip-teasé", -"strip-teasée", -"strip-teasées", -"strip-teasent", -"strip-teaser", -"strip-teasera", -"strip-teaserai", -"strip-teaseraient", -"strip-teaserais", -"strip-teaserait", -"strip-teaseras", -"strip-teasèrent", -"strip-teaserez", -"strip-teaseriez", -"strip-teaserions", -"strip-teaserons", -"strip-teaseront", -"strip-teases", -"strip-teasés", -"strip-teaseurs", -"strip-teaseuse", -"strip-teaseuses", -"strip-teasez", -"strip-teasiez", -"strip-teasions", -"strip-teasons", -"stroke-play", -"strom-apparat", +"Straßlach-Dingharting", "Strombeek-Bever", -"struggle-for-life", -"struggle-for-lifes", -"stud-book", -"Stüdenitz-Schönermark", -"stuffing-box", +"Strépy-Bracquegnies", "Stutzheim-Offenheim", -"stylo-bille", -"stylo-billes", -"stylo-feutre", -"stylo-glosse", -"stylo-gomme", -"stylo-pistolet", -"stylo-plume", -"stylos-feutres", -"stylos-gommes", -"stylo-souris", -"stylos-plume", -"stylos-souris", +"Stéphano-Carladésien", +"Stéphano-Carladésienne", +"Stéphano-Carladésiennes", +"Stéphano-Carladésiens", +"Stüdenitz-Schönermark", "Suaucourt-et-Pisseloup", -"subrogés-tuteurs", -"subrogé-tuteur", -"suce-bœuf", -"suce-boules", -"suce-fleur", -"suce-fleurs", -"suce-goulot", -"suce-goulots", -"suce-médailles", -"Sucé-sur-Erdre", "Suc-et-Sentenac", "Sucy-en-Brie", -"sudoro-algique", -"Súdwest-Fryslân", -"suédo-américain", -"suédo-américaine", -"suédo-américaines", -"suédo-américains", +"Sucé-sur-Erdre", "Suilly-la-Tour", "Suisse-Saxonne-Monts-Métallifères-de-l'Est", -"suivez-moi-jeune-homme", "Suizy-le-Franc", "Sukow-Levitzow", -"sulfo-margarique", "Sully-la-Chapelle", "Sully-sur-Loire", "Sulzbach-Laufen", "Sulzbach-Rosenberg", -"suméro-akkadien", -"suméro-akkadienne", -"suméro-akkadiennes", -"suméro-akkadiens", "Sunbury-on-Thames", -"super-8", -"support-chaussettes", -"supports-chaussettes", -"supra-axillaire", -"supra-axillaires", -"supra-caudal", -"supra-caudale", -"supra-caudales", -"supra-caudaux", -"supra-épineux", -"surdi-mutité", -"surdi-mutités", -"suro-pédieuse", -"suro-pédieuses", -"suro-pédieux", -"surprise-partie", -"surprise-parties", -"surprises-parties", -"surveillant-général", "Sury-aux-Bois", "Sury-en-Léré", "Sury-en-Vaux", -"Sury-ès-Bois", "Sury-le-Comtal", "Sury-près-Léré", -"sus-caudal", -"sus-cité", -"sus-coccygien", -"sus-dominante", -"sus-dominantes", -"sus-épineux", -"sus-hépatique", -"sus-hépatiques", -"sus-hyoïdien", -"sus-jacent", -"sus-jacents", -"sus-maxillo-labial", -"sus-maxillo-nasal", -"sus-métatarsien", -"sus-métatarsienne", -"sus-métatarsiennes", -"sus-métatarsiens", -"sus-naseau", -"sus-naso-labial", -"sus-pied", -"sus-pubio-fémoral", +"Sury-ès-Bois", "Sus-Saint-Léger", -"sus-tarsien", -"sus-tarsienne", -"sus-tarsiennes", -"sus-tarsiens", -"sus-tentoriel", -"sus-tentorielle", -"sus-tentorielles", -"sus-tentoriels", -"sus-tonique", -"su-sucre", -"su-sucres", "Sutton-in-Ashfield", "Sutz-Lattrigen", "Suze-la-Rousse", -"S.-W.", -"sweat-shirt", -"sweat-shirts", +"Sylvains-Lès-Moulins", "Sylvains-les-Moulins", -"syndesmo-pharyngien", "Syr-Daria", -"syro-chaldaïque", -"syro-chaldéen", -"syro-chaldéens", -"syro-saoudien", -"systèmes-clés", -"tabagn's", +"Sère-Lanso", +"Sère-Rustaing", +"Sère-en-Lavedan", +"Sèvres-Anxaumont", +"Sébazac-Concourès", +"Séez-Mesnil", +"Ségrie-Fontaine", +"Ségur-le-Château", +"Ségur-les-Villas", +"Séméacq-Blachon", +"Sémézies-Cachan", +"Sénaillac-Latronquière", +"Sénaillac-Lauzès", +"Sépeaux-Saint Romain", +"Séquano-Dionysien", +"Séranvillers-Forenville", +"Sérignac-Péboudou", +"Sérignac-sur-Garonne", +"Sérignan-du-Comtat", +"Séry-Magneval", +"Séry-lès-Mézières", +"Sérézin-de-la-Tour", +"Sérézin-du-Rhône", +"Sévignacq-Meyracq", +"Sévignacq-Thèze", +"Sévigny-Waleppe", +"Sévigny-la-Forêt", +"Sévérac d'Aveyron", +"Sévérac-d'Aveyron", +"Sévérac-l'Eglise", +"Sévérac-l'Église", +"Sévérac-le-Château", +"Súdwest-Fryslân", +"T'ien-ngan-men", +"T-SQL", +"T-calculable", +"T-calculables", +"T-shirt", +"T-shirts", +"Ta-Nehisi", "Tabaille-Usquain", "Taben-Rodt", -"table-bureau", -"tables-bureaux", -"tac-tac", "Tadousse-Ussau", "Taglio-Isolaccio", "Tahiti-Iti", "Tahu-Ata", "Taiarapu-Est", "Taiarapu-Ouest", -"tai-kadai", -"taï-kadaï", -"taï-le", -"taille-crayon", -"taille-crayons", -"taille-douce", -"taille-haie", -"taille-haies", -"taille-mèche", -"taille-mèches", -"taille-mer", -"taille-mers", -"taille-plume", -"taille-plumes", -"taille-pré", -"taille-prés", -"tailles-douces", -"taille-vent", -"taille-vents", "Tain-l'Hermitage", -"taï-nüa", "Taisnières-en-Thiérache", "Taisnières-sur-Hon", "Taizé-Aizie", -"taki-taki", -"talco-micacé", -"talco-quartzeux", -"talkies-walkies", -"talkie-walkie", -"talkie-walkies", -"talk-show", "Talloires-Montmin", "Tallud-Sainte-Gemme", "Talmont-Saint-Hilaire", "Talmont-sur-Gironde", "Talus-Saint-Prix", -"taly-pen", -"taly-pens", "Tambach-Dietharz", -"tambour-major", -"tambours-majors", "Tamnay-en-Bazois", -"tams-tams", -"tam-tam", -"tam-tams", -"Ta-Nehisi", "Tanghin-Dassouri", "Tannerre-en-Puisaye", -"tao-taï", -"tao-taïs", -"tape-à-l'oeil", -"tape-à-l'œil", -"tape-beurre", -"tape-beurres", -"tape-cul", -"tape-culs", -"tape-dur", -"tape-durs", -"tapis-brosse", -"tapis-de-caoutchouté", -"tapis-franc", -"tapis-francs", -"tapis-luge", -"tapis-luges", -"tapis-plain", "Taponnat-Fleurignac", "Tarascon-sur-Ariège", "Tarascon-sur-Rhône", "Tarawa-Sud", "Tardets-Sorholus", -"tard-venus", -"tarn-et-garonnais", "Tarn-et-Garonnais", -"tarn-et-garonnaise", "Tarn-et-Garonnaise", -"tarn-et-garonnaises", "Tarn-et-Garonnaises", "Tarn-et-Garonne", "Taron-Sadirac-Viellenave", -"tarso-métatarse", -"tarso-métatarsien", "Tart-l'Abbaye", "Tart-le-Bas", "Tart-le-Haut", -"tarton-raire", "Tassin-la-Demi-Lune", "Tataouine-les-Bains", -"tâte-au-pot", -"tâte-ferraille", -"tate-mono", -"tate-monos", -"tâte-poule", -"tâte-vin", -"tâte-vins", -"tau-fluvalinate", "Taulhac-près-le-Puy", -"taupe-grillon", -"taupes-grillons", "Tauriac-de-Camarès", "Tauriac-de-Naucelle", "Taurignan-Castet", @@ -23605,595 +16062,252 @@ FR_BASE_EXCEPTIONS = [ "Tauxières-Mutry", "Tavaux-et-Pontséricourt", "Taxat-Senat", -"taxi-auto", -"taxi-automobile", -"taxi-brousse", -"taxi-girl", -"taxi-girls", -"taxis-brousse", -"taxis-vélos", -"taxi-vélo", -"t-bone", -"t-bones", -"T-calculable", -"T-calculables", -"tchado-burkinabé", -"tchado-centrafricain", -"tchado-egyptien", -"tchado-lybien", -"tchado-soudano-lybien", -"tchéco-slovaque", -"Tchéco-slovaque", "Tchéco-Slovaque", -"tchéco-slovaques", -"Tchéco-slovaques", "Tchéco-Slovaques", -"tchin-tchin", -"tchou-tchou", -"teach-in", -"teach-ins", -"teen-ager", -"teen-agers", -"tee-shirt", -"tee-shirts", -"Teillay-le-Gaudin", +"Tchéco-slovaque", +"Tchéco-slovaques", "Teillay-Saint-Benoît", +"Teillay-le-Gaudin", "Teillet-Argenty", -"teinture-mère", -"teint-vin", -"teint-vins", "Teissières-de-Cornet", "Teissières-lès-Bouliès", "Tel-Aviv-Jaffa", "Telgruc-sur-Mer", "Tella-Sin", -"t-elle", "Tellières-le-Plessis", "Teltow-Fläming", "Temmen-Ringenwalde", -"témoins-clés", "Temple-Laguyon", "Templeuve-en-Pévèle", "Templeux-la-Fosse", "Templeux-le-Guérard", -"temporo-conchinien", -"temporo-superficiel", "Tenero-Contra", "Tensbüttel-Röst", -"tensio-actif", -"tente-abri", -"tente-ménagerie", -"tentes-ménageries", -"téra-ampère", -"téra-ampères", -"téra-électron-volt", -"téraélectron-volt", -"téra-électron-volts", -"téraélectron-volts", -"térawatt-heure", -"térawatt-heures", -"térawatts-heures", "Tercis-les-Bains", "Termes-d'Armagnac", "Ternant-les-Eaux", -"terno-annulaire", "Ternuay-Melay-et-Saint-Hilaire", "Terny-Sorny", -"terra-cotta", -"terra-forma", -"terra-formai", -"terra-formaient", -"terra-formais", -"terra-formait", -"terra-formâmes", -"terra-formant", -"terra-formas", -"terra-formasse", -"terra-formassent", -"terra-formasses", -"terra-formassiez", -"terra-formassions", -"terra-formât", -"terra-formâtes", -"terra-forme", -"terra-formé", -"terra-formée", -"terra-formées", -"terra-forment", -"terra-former", -"terra-formera", -"terra-formerai", -"terra-formeraient", -"terra-formerais", -"terra-formerait", -"terra-formeras", -"terra-formèrent", -"terra-formerez", -"terra-formeriez", -"terra-formerions", -"terra-formerons", -"terra-formeront", -"terra-formes", -"terra-formés", -"terra-formez", -"terra-formiez", -"terra-formions", -"terra-formons", -"Terrasson-la-Villedieu", "Terrasson-Lavilledieu", -"terre-à-terre", +"Terrasson-la-Villedieu", "Terre-Clapier", +"Terre-Natale", +"Terre-Neuve", +"Terre-Neuve-et-Labrador", +"Terre-Neuvien", +"Terre-Neuvien-et-Labradorien", +"Terre-Neuvienne", +"Terre-Neuvienne-et-Labradorienne", +"Terre-Neuviennes", +"Terre-Neuviennes-et-Labradoriennes", +"Terre-Neuviens", +"Terre-Neuviens-et-Labradoriens", "Terre-de-Bas", "Terre-de-Haut", "Terre-et-Marais", -"terre-grièpe", -"Terre-Natale", -"terre-neuva", -"terre-neuvas", -"terre-neuve", -"Terre-Neuve", -"Terre-Neuve-et-Labrador", -"terre-neuvien", -"Terre-Neuvien", -"Terre-Neuvien-et-Labradorien", -"terre-neuvienne", -"Terre-Neuvienne", -"Terre-Neuvienne-et-Labradorienne", -"terre-neuviennes", -"Terre-Neuviennes", -"Terre-Neuviennes-et-Labradoriennes", -"terre-neuviens", -"Terre-Neuviens", -"Terre-Neuviens-et-Labradoriens", -"terre-neuvier", -"terre-neuviers", -"terre-noix", -"terre-plein", -"terre-pleins", "Terres-de-Caux", -"terret-bourret", "Territoire-de-Belfort", "Terron-lès-Poix", "Terron-lès-Vendresse", "Terron-sur-Aisne", -"ter-ter", -"terza-rima", "Tessancourt-sur-Aubette", -"Tessé-Froulay", "Tessy-sur-Vire", -"test-match", -"test-matchs", +"Tessé-Froulay", "Test-Milon", -"test-objet", "Testorf-Steinfort", -"tête-à-queue", -"tête-à-tête", -"tête-bêche", -"tête-bleu", -"tête-chèvre", -"tête-de-bécasse", -"tête-de-chat", -"tête-de-chats", -"tête-de-cheval", -"tête-de-clou", -"tête-de-coq", -"tête-de-loup", -"tête-de-maure", -"tête-de-Maure", -"tête-de-méduse", -"Tête-de-Moine", -"tête-de-moineau", -"tête-de-More", -"tête-de-mort", -"tête-de-serpent", -"tête-de-soufre", -"Téteghem-Coudekerque-Village", -"tête-ronde", -"têtes-de-chat", -"têtes-de-clou", -"têtes-de-loup", -"têtes-de-Maure", -"têtes-de-méduse", -"têtes-de-moineau", -"têtes-de-mort", -"têtes-vertes", -"tête-verte", "Teting-sur-Nied", -"tétra-atomique", -"tétrachlorodibenzo-p-dioxine", -"tétrachlorodibenzo-p-dioxines", -"tétrachloro-isophtalonitrile", -"tette-chèvre", -"tette-chèvres", -"teufs-teufs", -"teuf-teuf", -"teuf-teufa", -"teuf-teufai", -"teuf-teufaient", -"teuf-teufais", -"teuf-teufait", -"teuf-teufâmes", -"teuf-teufant", -"teuf-teufas", -"teuf-teufasse", -"teuf-teufassent", -"teuf-teufasses", -"teuf-teufassiez", -"teuf-teufassions", -"teuf-teufât", -"teuf-teufâtes", -"teuf-teufe", -"teuf-teufé", -"teuf-teufent", -"teuf-teufer", -"teuf-teufera", -"teuf-teuferai", -"teuf-teuferaient", -"teuf-teuferais", -"teuf-teuferait", -"teuf-teuferas", -"teuf-teufèrent", -"teuf-teuferez", -"teuf-teuferiez", -"teuf-teuferions", -"teuf-teuferons", -"teuf-teuferont", -"teuf-teufes", -"teuf-teufez", -"teuf-teufiez", -"teuf-teufions", -"teuf-teufons", "Teurthéville-Bocage", "Teurthéville-Hague", "Thal-Drulingen", -"Thaleischweiler-Fröschen", "Thal-Marmoutier", +"Thaleischweiler-Fröschen", "Thaon-les-Vosges", "Theil-Rabier", "Theil-sur-Vanne", "Theix-Noyalo", -"Thélis-la-Combe", -"Théoule-sur-Mer", "Thermes-Magnoac", -"thêta-jointure", -"thêta-jointures", "Theuville-aux-Maillots", "Theuvy-Achères", "Thevet-Saint-Julien", "They-sous-Montfort", "They-sous-Vaudemont", -"Thézan-des-Corbières", -"Thézan-lès-Béziers", -"Thézey-Saint-Martin", -"Thézy-Glimont", -"thézy-glimontois", -"Thézy-Glimontois", -"thézy-glimontoise", -"Thézy-Glimontoise", -"thézy-glimontoises", -"Thézy-Glimontoises", "Thiaucourt-Regniéville", "Thiaville-sur-Meurthe", -"Thiéblemont-Farémont", "Thiel-sur-Acolin", "Thiers-sur-Thève", "Thierville-sur-Meuse", "Thieulloy-l'Abbaye", "Thieulloy-la-Ville", "Thieuloy-Saint-Antoine", -"thifensulfuron-méthyle", "Thil-Manneville", "Thil-sur-Arroux", "Thimert-Gâtelles", "Thimister-Clermont", -"thimistérien-clermontois", "Thimistérien-Clermontois", "Thimistérien-Clermontoise", "Thin-le-Moutier", "Thionville-sur-Opton", -"thiophanate-éthyl", -"thiophanate-méthyl", "Thiron-Gardais", "Thiverval-Grignon", "Thizy-les-Bourgs", +"Thiéblemont-Farémont", +"Thoirette-Coisia", "Thoiré-sous-Contensor", "Thoiré-sur-Dinan", -"Thoirette-Coisia", "Thoisy-la-Berchère", "Thoisy-le-Désert", "Thol-lès-Millières", "Thollon-les-Mémises", "Thomer-la-Sôgne", -"Thonnance-lès-Joinville", -"Thonnance-les-Moulins", -"Thonne-la-Long", -"Thonne-les-Près", -"Thonne-le-Thil", -"Thonon-les-Bains", "Thon-Samson", -"thon-samsonais", "Thon-Samsonais", "Thon-Samsonaise", +"Thonnance-les-Moulins", +"Thonnance-lès-Joinville", +"Thonne-la-Long", +"Thonne-le-Thil", +"Thonne-les-Près", +"Thonon-les-Bains", "Thorame-Basse", "Thorame-Haute", -"Thorée-les-Pins", -"thoré-folléen", -"Thoré-Folléen", -"thoré-folléenne", -"Thoré-Folléenne", -"thoré-folléennes", -"Thoré-Folléennes", -"thoré-folléens", -"Thoré-Folléens", -"Thoré-la-Rochette", -"Thorembais-les-Béguines", "Thorembais-Saint-Trond", +"Thorembais-les-Béguines", "Thorens-Glières", -"Thorey-en-Plaine", "Thorey-Lyautey", +"Thorey-en-Plaine", "Thorey-sous-Charny", "Thorey-sur-Ouche", -"Thorigné-d'Anjou", -"Thorigné-en-Charnie", -"Thorigné-Fouillard", -"Thorigné-sur-Dué", -"Thorigné-sur-Vilaine", -"Thorigny-sur-le-Mignon", "Thorigny-sur-Marne", "Thorigny-sur-Oreuse", +"Thorigny-sur-le-Mignon", +"Thorigné-Fouillard", +"Thorigné-d'Anjou", +"Thorigné-en-Charnie", +"Thorigné-sur-Dué", +"Thorigné-sur-Vilaine", "Thornaby-on-Tees", "Thornton-Cleveleys", -"Thouaré-sur-Loire", -"Thouarsais-Bouildroux", +"Thoré-Folléen", +"Thoré-Folléenne", +"Thoré-Folléennes", +"Thoré-Folléens", +"Thoré-la-Rochette", +"Thorée-les-Pins", "Thouars-sur-Arize", "Thouars-sur-Garonne", -"thoult-tronaisien", +"Thouarsais-Bouildroux", +"Thouaré-sur-Loire", "Thoult-Tronaisien", -"thoult-tronaisienne", "Thoult-Tronaisienne", -"thoult-tronaisiennes", "Thoult-Tronaisiennes", -"thoult-tronaisiens", "Thoult-Tronaisiens", -"Thoury-Férottes", "Thoury-Ferrottes", -"thraco-illyrienne", -"Thuès-Entre-Valls", +"Thoury-Férottes", "Thugny-Trugny", "Thuilley-aux-Groseilles", -"thuit-angevin", "Thuit-Angevin", -"thuit-angevine", "Thuit-Angevine", -"thuit-angevines", "Thuit-Angevines", -"thuit-angevins", "Thuit-Angevins", "Thuit-Hébert", -"thuit-signolais", "Thuit-Signolais", -"thuit-signolaise", "Thuit-Signolaise", -"thuit-signolaises", "Thuit-Signolaises", -"thuit-simérien", "Thuit-Simérien", -"thuit-simérienne", "Thuit-Simérienne", -"thuit-simériennes", "Thuit-Simériennes", -"thuit-simériens", "Thuit-Simériens", -"thun-episcopien", "Thun-Episcopien", -"thun-épiscopien", -"Thun-Épiscopien", "Thun-Episcopienne", -"thun-épiscopienne", -"Thun-Épiscopienne", "Thun-Episcopiennes", -"thun-épiscopiennes", -"Thun-Épiscopiennes", "Thun-Episcopiens", -"thun-épiscopiens", -"Thun-Épiscopiens", -"Thun-l'Evêque", -"Thun-l'Évêque", "Thun-Saint-Amand", "Thun-Saint-Martin", +"Thun-l'Evêque", +"Thun-l'Évêque", +"Thun-Épiscopien", +"Thun-Épiscopienne", +"Thun-Épiscopiennes", +"Thun-Épiscopiens", "Thurey-le-Mont", -"Thury-en-Valois", "Thury-Harcourt", +"Thury-en-Valois", "Thury-sous-Clermont", +"Thuès-Entre-Valls", "Thy-le-Bauduin", "Thy-le-Château", +"Thélis-la-Combe", +"Théoule-sur-Mer", +"Thézan-des-Corbières", +"Thézan-lès-Béziers", +"Thézey-Saint-Martin", +"Thézy-Glimont", +"Thézy-Glimontois", +"Thézy-Glimontoise", +"Thézy-Glimontoises", "Tian'anmen", -"tibéto-birman", -"tibéto-birmane", -"tibéto-birmanes", -"tibéto-birmans", -"tibio-malléolaire", "Tibiran-Jaunac", -"ticket-restaurant", -"ti-coune", -"ti-counes", -"tic-tac", -"tic-tacs", -"tic-tac-toe", -"ti-cul", -"tie-break", -"tie-breaks", "Tielt-Winge", -"T'ien-ngan-men", -"tierce-feuille", -"tierce-rime", -"tierces-rimes", "Tieste-Uragnoux", -"tiger-kidnappeur", -"tiger-kidnapping", -"tiger-kidnappings", "Tignieu-Jameyzieu", "Tigny-Noyelle", -"tigre-garou", -"tigres-garous", -"tiki-taka", -"t-il", "Til-Châtel", "Tillay-le-Péneux", "Tilleul-Dame-Agnès", -"tilleul-othonnais", "Tilleul-Othonnais", -"tilleul-othonnaise", "Tilleul-Othonnaise", -"tilleul-othonnaises", "Tilleul-Othonnaises", "Tillières-sur-Avre", -"Tilloy-et-Bellay", "Tilloy-Floriville", +"Tilloy-et-Bellay", +"Tilloy-lez-Cambrai", +"Tilloy-lez-Marchiennes", "Tilloy-lès-Conty", "Tilloy-lès-Hermaville", "Tilloy-lès-Mofflaines", -"Tilloy-lez-Cambrai", -"Tilloy-lez-Marchiennes", "Tilly-Capelle", "Tilly-la-Campagne", "Tilly-sur-Meuse", "Tilly-sur-Seulles", -"tilt-shift", -"timbre-amende", -"timbre-poste", -"timbre-quittance", -"timbres-amende", -"timbres-poste", -"timbres-quittances", -"timbre-taxe", -"time-lapse", -"time-lapses", -"time-sharing", -"time-sharings", "Tin-Akof", "Tincey-et-Pontrebeau", "Tinchebray-Bocage", "Tincourt-Boucly", "Tinizong-Rona", -"t'inquiète", -"tiou-tiou", -"tiou-tious", -"ti-papoute", -"ti-punch", -"ti-punchs", -"tira-tutto", "Tirent-Pontéjac", -"tireur-au-cul", -"tireurs-au-cul", -"tiroir-caisse", -"tiroirs-caisses", -"tissu-éponge", -"tissus-éponges", -"titan-cotte", -"titanico-ammonique", -"titanico-ammoniques", "Tite-Live", "Titisee-Neustadt", -"titre-service", -"titres-services", "Tizac-de-Curton", "Tizac-de-Lapouyade", -"toba-qom", "Tobel-Tägerschen", "Tocane-Saint-Apre", -"t'occupe", -"toc-feu", "Tocqueville-en-Caux", "Tocqueville-les-Murs", "Tocqueville-sur-Eu", -"toc-toc", -"toc-tocs", +"Togny-aux-Bœufs", "Togny-aux-Bœufs", -"t'oh", -"tohu-bohu", -"tohu-bohus", -"tohus-bohus", -"toi-même", -"toits-terrasses", -"toit-terrasse", -"tolclofos-méthyl", -"tombe-cartouche", -"tom-pouce", -"tom-tom", -"tom-toms", -"t-on", "Tongre-Notre-Dame", "Tongre-Saint-Martin", "Tonnay-Boutonne", "Tonnay-Charente", "Tonnegrande-Montsinery", -"tonne-grenoir", -"tonne-mètre", -"top-down", -"top-model", -"top-modèle", -"top-modèles", -"top-models", -"topo-guide", -"topo-guides", -"top-secret", -"top-secrets", -"toque-feu", -"Torcé-en-Vallée", -"Torcé-Viviers-en-Charnie", -"torche-cul", -"torche-culs", -"torche-fer", -"torche-pertuis", -"torche-pin", -"torche-pinceau", -"torche-pinceaux", -"torche-pins", "Torcy-en-Valois", "Torcy-et-Pouligny", "Torcy-le-Grand", "Torcy-le-Petit", -"tord-boyau", -"tord-boyaux", -"tord-nez", +"Torcé-Viviers-en-Charnie", +"Torcé-en-Vallée", "Torgelow-Holländerei", "Torigni-sur-Vire", "Torigny-les-Villes", -"tori-i", "Torre-Cardela", "Torre-serona", "Torricella-Taverne", -"torse-poil", -"torse-poils", "Torteval-Quesnay", -"tortue-alligator", -"tortue-boite", -"tortue-boîte", -"tortue-duc", -"tortues-alligators", -"tortues-boites", -"tortues-boîtes", -"tortues-ducs", -"tosa-inu", "Toscolano-Maderno", -"tote-bag", -"tote-bags", -"tôt-fait", -"tôt-faits", -"touch-and-go", -"touche-à-tout", -"touche-pipi", -"touche-touche", -"Touët-de-l'Escarène", -"Touët-sur-Var", "Touffreville-la-Cable", "Touffreville-la-Corbeline", "Touffreville-sur-Eu", -"touille-boeuf", -"touille-bœuf", -"touille-boeufs", -"touille-bœufs", "Touillon-et-Loutelet", "Toulis-et-Attencourt", "Toulon-la-Montagne", @@ -24201,70 +16315,34 @@ FR_BASE_EXCEPTIONS = [ "Toulon-sur-Arroux", "Toulouse-le-Château", "Toulx-Sainte-Croix", -"Tourailles-sous-Bois", -"tour-à-tour", -"Tourcelles-Chaumont", -"Tourcelles-Chaumont-Quilly-et-Chardeny", "Tour-de-Faure", "Tour-en-Bessin", "Tour-en-Sologne", +"Tourailles-sous-Bois", +"Tourcelles-Chaumont", +"Tourcelles-Chaumont-Quilly-et-Chardeny", "Tourette-du-Château", -"Tourinnes-la-Grosse", "Tourinnes-Saint-Lambert", -"tour-minute", +"Tourinnes-la-Grosse", "Tournai-sur-Dive", "Tournan-en-Brie", "Tournay-sur-Odon", -"tourne-à-gauche", -"tourne-au-vent", -"tourne-case", -"tourne-cases", -"tourne-disque", -"tourne-disques", "Tournedos-Bois-Hubert", "Tournedos-sur-Seine", -"tourne-feuille", -"tourne-feuilles", -"tourne-feuillet", -"tourne-feuillets", -"tourne-fil", -"tourne-fils", -"tourne-gants", "Tournehem-sur-la-Hem", -"tourne-motte", -"tourne-mottes", -"tourne-oreille", -"tourne-oreilles", -"tourne-pierres", -"tourne-soc", -"tourne-socs", -"tourneur-fraiseur", -"tourneurs-fraiseurs", -"tourne-vent", -"tourne-vents", -"Tournon-d'Agenais", "Tournon-Saint-Martin", "Tournon-Saint-Pierre", +"Tournon-d'Agenais", "Tournon-sur-Rhône", "Tournous-Darré", "Tournous-Devant", -"tour-opérateur", -"tour-opérateurs", -"tour-opératrice", -"tour-opératrices", "Tourouvre-au-Perche", "Tourrette-Levens", "Tourrettes-sur-Loup", "Tours-en-Savoie", "Tours-en-Vimeu", -"tours-minute", -"tours-opérateurs", -"tours-opératrices", -"tours-sur-marnais", "Tours-sur-Marnais", -"tours-sur-marnaise", "Tours-sur-Marnaise", -"tours-sur-marnaises", "Tours-sur-Marnaises", "Tours-sur-Marne", "Tours-sur-Meymont", @@ -24281,295 +16359,90 @@ FR_BASE_EXCEPTIONS = [ "Toury-sur-Jour", "Tourzel-Ronzières", "Toussus-le-Noble", -"tout-à-fait", -"tout-à-la-rue", -"tout-à-l'égout", -"tout-blanc", -"tout-blancs", -"tout-communication", -"tout-connaissant", -"toute-bonne", -"toute-bonté", -"toute-cousue", -"toute-épice", -"tout-ensemble", -"tout-en-un", -"toute-petite", -"toute-présence", -"toute-puissance", -"toute-puissante", -"toute-saine", -"toutes-boîtes", -"toutes-bonnes", -"toute-science", -"toutes-petites", -"toutes-puissantes", -"toutes-saines", -"toutes-tables", -"toutes-venues", -"toute-table", -"toute-venue", -"tout-fait", -"tout-faits", -"tout-fécond", -"tout-Londres", "Tout-Paris", -"tout-parisien", -"tout-parisienne", -"tout-parisiennes", -"tout-parisiens", -"tout-petit", -"tout-petits", -"tout-puissant", "Tout-Puissant", -"tout-puissants", -"tout-terrain", -"tout-venant", -"tout-venu", -"toxi-infectieux", -"toxi-infection", -"toxi-infections", -"toy-terrier", +"Touët-de-l'Escarène", +"Touët-sur-Var", "Toy-Viam", "Traben-Trarbach", -"trace-bouche", -"trace-roulis", -"trace-sautereau", -"trace-vague", -"trachée-artère", -"trachélo-occipital", -"trachéo-bronchite", -"trachéo-bronchites", "Tracy-Bocage", "Tracy-le-Mont", "Tracy-le-Val", "Tracy-sur-Loire", "Tracy-sur-Mer", -"trade-union", -"trade-unionisme", -"trade-unionismes", -"trade-unions", -"tragi-comédie", -"tragi-comédies", -"tragi-comique", -"tragi-comiques", -"traîne-bâton", -"traine-buche", -"traîne-bûche", -"traine-buches", -"traîne-bûches", -"traîne-buisson", -"traîne-charrue", -"traîne-la-patte", -"traîne-lattes", -"traîne-malheur", -"traîne-misère", -"traîne-patins", -"traîne-potence", -"traine-ruisseau", -"traîne-ruisseau", -"traine-savate", -"traîne-savate", -"traine-savates", -"traîne-savates", -"traîne-semelle", -"traîne-semelles", -"trains-trams", -"train-train", -"train-trains", -"train-tram", -"trait-d'union", -"trait-d'unioné", -"trait-track", "Tramont-Emy", -"Tramont-Émy", "Tramont-Lassus", "Tramont-Saint-André", -"trams-trains", -"tram-train", -"tranchées-abris", -"tranche-maçonné", -"tranche-montagne", -"tranche-montagnes", -"tranche-papier", -"tranche-tête", +"Tramont-Émy", "Tranqueville-Graux", -"tran-tran", +"Trans-en-Provence", +"Trans-la-Forêt", +"Trans-sur-Erdre", "Traubach-le-Bas", "Traubach-le-Haut", "Travedona-Monate", -"Trébons-de-Luchon", -"Trébons-sur-la-Grasse", -"Trédrez-Locquémeau", "Treffort-Cuisiat", -"tré-flip", -"tré-flips", "Treilles-en-Gâtinais", "Treis-Karden", "Treize-Septiers", "Treize-Vents", -"Trélou-sur-Marne", "Tremblay-en-France", -"Tremblay-lès-Gonesse", "Tremblay-les-Villages", +"Tremblay-lès-Gonesse", "Tremblois-lès-Carignan", "Tremblois-lès-Rocroi", -"Trémont-sur-Saulx", -"Trémouille-Saint-Loup", -"trench-coat", -"trench-coats", -"trente-cinq", -"trente-deux", -"trente-deuxième", -"trente-deuxièmes", -"trente-deuzain", -"trente-deuzains", -"trente-deuzet", -"trente-deuzets", -"trente-douze", -"trente-et-un", -"trente-et-une", -"trente-et-unième", -"trente-et-unièmes", -"trente-huit", -"trente-neuf", -"trente-neuvième", -"trente-quatre", -"trente-sept", -"trente-six", -"trente-trois", -"trente-troisième", "Trentin-Haut-Adige", "Trentola-Ducenta", -"trépan-benne", -"trépan-bennes", "Treschenu-Creyers", -"très-chrétien", -"tré-sept", -"très-haut", -"Très-Haut", "Trespoux-Rassiels", "Treuzy-Levelay", -"Trèves-Cunault", -"Trèves-Sarrebourg", -"Trévou-Tréguignec", "Triac-Lautrait", -"tribénuron-méthyle", -"tribo-électricité", -"tribo-électricités", -"tribo-électrique", -"tribo-électriques", -"trichloro-nitrométhane", -"trichloro-trinitro-benzène", -"tric-trac", -"tric-tracs", "Trie-Château", "Trie-la-Ville", +"Trie-sur-Baïse", "Triel-sur-Seine", "Triembach-au-Val", -"Trie-sur-Baïse", "Triffouilly-les-Oies", -"triflusulfuron-méthyle", "Trifouillis-les-Oies", "Trifouilly-les-Oies", -"trinexapac-éthyl", "Trinité-et-Tobago", -"trinitro-cellulose", -"trinitro-celluloses", -"tripe-madame", -"triple-croche", -"triples-croches", -"trique-madame", -"tris-mal", -"tris-male", -"tris-males", -"tris-maux", "Trith-Saint-Léger", "Tritteling-Redlach", "Trizay-Coutretot-Saint-Serge", "Trizay-lès-Bonneval", "Trockenborn-Wolfersdorf", "Trocy-en-Multien", -"trois-bassinois", "Trois-Bassinois", -"trois-bassinoise", "Trois-Bassinoise", -"trois-bassinoises", "Trois-Bassinoises", -"trois-crayons", -"trois-épines", "Trois-Fonds", "Trois-Fontaines", "Trois-Fontaines-l'Abbaye", -"Troisfontaines-la-Ville", -"trois-huit", -"trois-mâts", -"trois-mâts-goélettes", "Trois-Monts", "Trois-Palis", -"trois-pierrais", "Trois-Pierrais", -"trois-pierraise", "Trois-Pierraise", -"trois-pierraises", "Trois-Pierraises", "Trois-Pistolet", "Trois-Pistolien", "Trois-Pistolois", -"trois-ponts", "Trois-Ponts", "Trois-Puits", -"trois-quarts", "Trois-Riverain", "Trois-Rives", "Trois-Rivières", -"trois-riviérien", "Trois-Riviérien", -"trois-riviérienne", "Trois-Riviérienne", -"trois-riviériennes", "Trois-Riviériennes", -"trois-riviériens", "Trois-Riviériens", -"trois-roues", -"trois-six", -"trois-trois", -"Trois-Vèvres", "Trois-Villes", -"trompe-cheval", -"trompe-couillon", -"trompe-la-mort", -"trompe-l'oeil", -"trompe-l'œil", -"trompe-oreilles", -"trompe-valet", +"Trois-Vèvres", +"Troisfontaines-la-Ville", "Tronville-en-Barrois", -"trop-bu", -"trop-payé", -"trop-payés", -"trop-perçu", -"trop-perçus", -"trop-plein", -"trop-pleins", "Trosly-Breuil", "Trosly-Loire", -"trotte-chemin", -"trotte-menu", "Trouan-le-Grand", -"trouble-fête", -"trouble-fêtes", "Trouley-Labarthe", -"trousse-barre", -"trousse-barres", -"trousse-pet", -"trousse-pète", -"trousse-pètes", -"trousse-pets", -"trousse-pied", -"trousse-pieds", -"trousse-queue", -"trousse-queues", -"trousse-traits", "Trouville-la-Haule", "Trouville-sur-Mer", "Troye-d'Ariège", @@ -24578,119 +16451,54 @@ FR_BASE_EXCEPTIONS = [ "Trucy-sur-Yonne", "Truttemer-le-Grand", "Truttemer-le-Petit", +"Très-Haut", +"Trèves-Cunault", +"Trèves-Sarrebourg", +"Trébons-de-Luchon", +"Trébons-sur-la-Grasse", +"Trédrez-Locquémeau", +"Trélou-sur-Marne", +"Trémont-sur-Saulx", +"Trémouille-Saint-Loup", +"Trévou-Tréguignec", "Tschiertschen-Praden", -"tsé-tsé", -"tsé-tsés", -"t-shirt", -"T-shirt", -"t-shirts", -"T-shirts", -"tsoin-tsoin", -"tsouin-tsouin", -"T-SQL", -"tss-tss", -"tta-kun", -"tta-kuns", -"ttun-ttun", -"ttun-ttuns", -"tubéro-infundibulaire", -"tubéro-infundibulaires", -"tue-brebis", -"tue-chien", -"tue-chiens", -"tue-diable", -"tue-diables", -"tue-l'amour", -"tue-loup", -"tue-loups", -"tue-mouche", -"tue-mouches", -"tue-poule", -"tue-teignes", "Tue-Vaques", -"tue-vent", -"Tugéras-Saint-Maurice", "Tugny-et-Pont", -"Tümlauer-Koog", -"tuniso-égypto-lybien", -"tupi-guarani", +"Tugéras-Saint-Maurice", "Tupin-et-Semons", -"turbo-alternateur", -"turbo-alternateurs", -"turbo-capitalisme", -"turbo-capitalismes", -"turbo-compresseur", -"turbo-compresseurs", -"turbo-prof", -"turbo-profs", -"turco-coréen", -"turco-mongol", -"turco-persan", -"turco-syrien", "Turing-calculable", "Turing-calculables", -"turn-over", "Turnow-Preilack", "Turquestein-Blancrupt", -"tutti-frutti", -"tu-tu-ban-ban", -"tux-zillertal", -"twin-set", -"twin-sets", -"tz'utujil", +"Téteghem-Coudekerque-Village", +"Tête-de-Moine", +"Tümlauer-Koog", +"U-turn", +"U-turns", +"UTF-8", "Ua-Huka", "Ua-Pou", -"Übach-Palenberg", "Ubaye-Serre-Ponçon", -"über-célèbre", -"über-célèbres", "Ubstadt-Weiher", "Uchacq-et-Parentis", -"u-commerce", "Uebigau-Wahrenbrück", "Uecker-Randow", "Uesslingen-Buch", "Ugao-Miraballes", "Uggiate-Trevano", -"Ugny-le-Gay", "Ugny-l'Equipée", "Ugny-l'Équipée", +"Ugny-le-Gay", "Ugny-sur-Meuse", "Uhart-Cize", -"Uharte-Arakil", "Uhart-Mixe", +"Uharte-Arakil", "Uhldingen-Mühlhofen", -"Ühlingen-Birkendorf", "Uhlstädt-Kirchhasel", -"ukiyo-e", -"ukiyo-es", "Ully-Saint-Georges", "Uncey-le-Franc", -"unda-maris", -"une-deux", -"uni-dimensionnel", -"uni-dimensionnelle", -"uni-dimensionnelles", -"uni-dimensionnels", -"uni-modal", -"uni-sonore", -"uni-sonores", -"unité-souris", -"unités-souris", -"univers-bloc", -"univers-île", -"univers-îles", "Unstrut-Hainich", -"upa-upa", "Upgant-Schott", -"urane-mica", -"uranes-micas", -"urétro-cystotomie", -"urétro-cystotomies", -"uro-génital", -"uro-génitale", -"uro-génitales", -"uro-génitaux", "Urou-et-Crennes", "Urroz-Villa", "Urtenen-Schönbühl", @@ -24703,100 +16511,64 @@ FR_BASE_EXCEPTIONS = [ "Usson-du-Poitou", "Usson-en-Forez", "Ussy-sur-Marne", -"utéro-lombaire", -"utéro-ovarien", -"utéro-ovarienne", -"utéro-ovariennes", -"utéro-ovariens", -"utéro-placentaire", -"utéro-tubaire", -"utéro-vaginal", -"utéro-vaginale", -"utéro-vaginales", -"utéro-vaginaux", -"UTF-8", -"uto-aztèque", -"uto-aztèques", -"U-turn", -"U-turns", -"uva-ursi", -"uva-ursis", "Uvernet-Fours", "Uzay-le-Venon", -"Vabres-l'Abbaye", "Vabre-Tizac", -"vache-biche", -"vache-garou", -"Vachères-en-Quint", +"Vabres-l'Abbaye", "Vacheresses-les-Basses", -"vaches-biches", -"vaches-garous", +"Vachères-en-Quint", "Vacognes-Neuilly", "Vacquerie-le-Boucq", "Vacqueriette-Erquières", -"vade-in-pace", -"va-de-la-gueule", -"vade-mecum", -"va-de-pied", -"vaeakau-taumako", -"vaeakau-taumakos", -"va-et-vient", -"vagino-vésical", "Vahl-Ebersing", "Vahl-lès-Bénestroff", "Vahl-lès-Faulquemont", "Vaihingen-sur-l'Enz", "Vailly-sur-Aisne", "Vailly-sur-Sauldre", -"vaine-pâture", +"Vair-sur-Loire", "Vaire-Arcier", "Vaire-le-Petit", "Vaire-sous-Corbie", "Vaires-sur-Marne", -"Vair-sur-Loire", "Vaison-la-Romaine", "Vaivre-et-Montoille", +"Val Buëch-Méouge", +"Val d'Arcomie", +"Val d'Issoire", +"Val d'Oronaye", +"Val d'Oust", +"Val d'épy", "Val-Alainois", -"Val-au-Perche", -"Val-Bélairien", "Val-Brillantois", +"Val-Bélairien", "Val-Cenis", +"Val-Davidois", +"Val-Fouzon", +"Val-Jolois", +"Val-Maravel", +"Val-Meer", +"Val-Mont", +"Val-Morinois", +"Val-Mésangeois", +"Val-Mésangeoise", +"Val-Mésangeoises", +"Val-Racinois", +"Val-Revermont", +"Val-Saint-Germinois", +"Val-Saint-Germinoise", +"Val-Saint-Germinoises", +"Val-Saint-Pierrais", +"Val-Saint-Pierraise", +"Val-Saint-Pierraises", +"Val-Sennevillois", +"Val-Sonnette", +"Val-Suzon", +"Val-au-Perche", "Val-d'Aoste", "Val-d'Auzon", -"Val-Davidois", -"Val-de-Bride", -"Val-de-Chalvagne", -"Val-de-Fier", -"Valdegovía-Gaubea", -"Val-de-la-Haye", -"val-de-marnais", -"Val-de-Marne", -"Val-de-Mercy", -"Val-de-Meuse", -"Valdemoro-Sierra", -"Valdeolmos-Alalpardo", "Val-d'Epy", -"Val-d'Épy", -"Val-de-Reuil", -"Val-de-Roulans", -"Val-de-Ruz", -"val-de-saânais", -"Val-de-Saânais", -"val-de-saânaise", -"Val-de-Saânaise", -"val-de-saânaises", -"Val-de-Saânaises", -"Val-de-Saâne", -"Val-des-Marais", "Val-d'Espoirien", -"Val-des-Prés", -"Val-de-Travers", -"Valde-Ucieza", -"Val-de-Vesle", -"Val-de-Vie", -"Val-de-Vière", -"Val-de-Virvée", -"Valdieu-Lutran", "Val-d'Illiez", "Val-d'Isère", "Val-d'Izé", @@ -24806,109 +16578,98 @@ FR_BASE_EXCEPTIONS = [ "Val-d'Oisiennes", "Val-d'Oisiens", "Val-d'Orger", -"Vald'orien", "Val-d'Orien", "Val-d'Ornain", "Val-d'Oust", +"Val-d'Épy", +"Val-de-Bride", +"Val-de-Chalvagne", +"Val-de-Fier", +"Val-de-Marne", +"Val-de-Mercy", +"Val-de-Meuse", +"Val-de-Reuil", +"Val-de-Roulans", +"Val-de-Ruz", +"Val-de-Saânais", +"Val-de-Saânaise", +"Val-de-Saânaises", +"Val-de-Saâne", +"Val-de-Travers", +"Val-de-Vesle", +"Val-de-Vie", +"Val-de-Virvée", +"Val-de-Vière", +"Val-de-la-Haye", +"Val-des-Marais", +"Val-des-Prés", "Val-du-Layon", +"Val-et-Châtillon", +"Vald'orien", +"Valde-Ucieza", +"Valdegovía-Gaubea", +"Valdemoro-Sierra", +"Valdeolmos-Alalpardo", +"Valdieu-Lutran", "Valence-d'Albigeois", "Valence-en-Brie", -"valence-gramme", -"valence-grammes", "Valence-sur-Baïse", -"valet-à-patin", -"Val-et-Châtillon", -"valet-de-pied", -"valets-à-patin", -"valets-de-pied", "Valeyres-sous-Montagny", "Valeyres-sous-Rances", "Valeyres-sous-Ursins", "Valfin-lès-Saint-Claude", "Valfin-sur-Valouse", -"Val-Fouzon", -"Val-Jolois", "Valkenburg-Houthem", +"Vall-llobrega", "Vallant-Saint-Georges", "Valle-d'Alesani", +"Valle-d'Orezza", "Valle-di-Campoloro", "Valle-di-Mezzana", "Valle-di-Rostino", -"Valle-d'Orezza", -"Vallerois-le-Bois", "Vallerois-Lorioz", +"Vallerois-le-Bois", "Valleroy-aux-Saules", "Valleroy-le-Sec", "Vallières-les-Grandes", "Vallières-lès-Metz", -"Vall-llobrega", "Valloire-sur-Cisse", -"Vallon-en-Sully", "Vallon-Pont-d'Arc", +"Vallon-en-Sully", "Vallon-sur-Gée", "Vallouise-Pelvoux", -"Val-Maravel", -"Val-Meer", -"val-mésangeois", -"Val-Mésangeois", -"val-mésangeoise", -"Val-Mésangeoise", -"val-mésangeoises", -"Val-Mésangeoises", -"Val-Mont", -"Val-Morinois", -"Val-Racinois", "Valras-Plage", -"Val-Revermont", -"val-saint-germinois", -"Val-Saint-Germinois", -"val-saint-germinoise", -"Val-Saint-Germinoise", -"val-saint-germinoises", -"Val-Saint-Germinoises", -"val-saint-pierrais", -"Val-Saint-Pierrais", -"val-saint-pierraise", -"Val-Saint-Pierraise", -"val-saint-pierraises", -"Val-Saint-Pierraises", "Vals-des-Tilles", -"valse-hésitation", -"Val-Sennevillois", -"valses-hésitations", "Vals-le-Chastel", "Vals-les-Bains", -"Val-Sonnette", "Vals-près-le-Puy", -"Val-Suzon", "Valverde-Enrique", -"Valzin-en-Petite-Montagne", "Valz-sous-Châteauneuf", +"Valzin-en-Petite-Montagne", "Vanault-le-Châtel", "Vanault-les-Dames", "Vandenesse-en-Auxois", +"Vandœuvre-lès-Nancy", "Vandœuvre-lès-Nancy", -"vanity-case", -"vanity-cases", "Vannes-le-Châtel", "Vannes-sur-Cosson", "Vantoux-et-Longevelle", "Vantoux-lès-Dijon", -"va-nu-pieds", -"va-outre", "Varces-Allières-et-Risset", "Varengeville-sur-Mer", -"Varenne-l'Arconce", "Varenne-Saint-Germain", +"Varenne-l'Arconce", +"Varenne-sur-le-Doubs", "Varennes-Changy", -"Varennes-en-Argonne", "Varennes-Jarcy", +"Varennes-Saint-Honorat", +"Varennes-Saint-Sauveur", +"Varennes-Vauzelles", +"Varennes-en-Argonne", "Varennes-le-Grand", "Varennes-lès-Mâcon", "Varennes-lès-Narcy", "Varennes-lès-Nevers", -"Varennes-Saint-Honorat", -"Varennes-Saint-Sauveur", "Varennes-sous-Dun", "Varennes-sur-Allier", "Varennes-sur-Amance", @@ -24918,46 +16679,21 @@ FR_BASE_EXCEPTIONS = [ "Varennes-sur-Seine", "Varennes-sur-Tèche", "Varennes-sur-Usson", -"Varenne-sur-le-Doubs", -"Varennes-Vauzelles", "Varmie-Mazurie", "Varneville-Bretteville", "Varois-et-Chaignot", "Vars-sur-Roseix", -"vasculo-nerveux", -"vaso-constricteur", -"vaso-constricteurs", -"vaso-constriction", -"vaso-constrictions", -"vaso-dilatateur", -"vaso-dilatateurs", -"vaso-dilatation", -"vaso-dilatations", -"vaso-intestinal", -"vaso-intestinale", -"vaso-intestinales", -"vaso-intestinaux", -"vaso-moteur", -"vaso-motrice", "Vassieux-en-Vercors", "Vassimont-et-Chapelaine", "Vassy-lès-Avallon", "Vassy-sous-Pisy", -"vas-y", -"va-te-laver", -"va-t-en", -"va-t'en", -"va-t-en-guerre", -"vaterite-A", -"vaterite-As", -"va-tout", "Vattetot-sous-Beaumont", "Vattetot-sur-Mer", "Vatteville-la-Rue", "Vaucelles-et-Beffecourt", +"Vauchelles-les-Quesnoy", "Vauchelles-lès-Authie", "Vauchelles-lès-Domart", -"Vauchelles-les-Quesnoy", "Vauclerc-et-la-Vallée-Foulon", "Vauconcourt-Nervezain", "Vaudeville-le-Haut", @@ -24966,22 +16702,26 @@ FR_BASE_EXCEPTIONS = [ "Vaulnaveys-le-Bas", "Vaulnaveys-le-Haut", "Vault-de-Lugny", -"Vaulx-en-Velin", "Vaulx-Milieu", "Vaulx-Vraucourt", +"Vaulx-en-Velin", "Vaunaveys-la-Rochette", "Vaux-Andigny", "Vaux-Champagne", -"vaux-champenois", "Vaux-Champenois", -"vaux-champenoise", "Vaux-Champenoise", -"vaux-champenoises", "Vaux-Champenoises", "Vaux-Chavanne", -"vaux-chavannois", "Vaux-Chavannois", "Vaux-Chavannoise", +"Vaux-Lavalette", +"Vaux-Marquenneville", +"Vaux-Montreuil", +"Vaux-Rouillac", +"Vaux-Saules", +"Vaux-Sûrois", +"Vaux-Sûroise", +"Vaux-Villaine", "Vaux-d'Amognes", "Vaux-devant-Damloup", "Vaux-en-Amiénois", @@ -24996,20 +16736,15 @@ FR_BASE_EXCEPTIONS = [ "Vaux-la-Douce", "Vaux-la-Grande", "Vaux-la-Petite", -"Vaux-Lavalette", "Vaux-le-Moncelot", "Vaux-le-Pénil", +"Vaux-les-Prés", +"Vaux-lez-Rosières", "Vaux-lès-Mouron", "Vaux-lès-Mouzon", "Vaux-lès-Palameix", -"Vaux-les-Prés", "Vaux-lès-Rubigny", "Vaux-lès-Saint-Claude", -"Vaux-lez-Rosières", -"Vaux-Marquenneville", -"Vaux-Montreuil", -"Vaux-Rouillac", -"Vaux-Saules", "Vaux-sous-Aubigny", "Vaux-sous-Bourcq", "Vaux-sous-Chèvremont", @@ -25020,9 +16755,6 @@ FR_BASE_EXCEPTIONS = [ "Vaux-sur-Lunain", "Vaux-sur-Mer", "Vaux-sur-Morges", -"vaux-sûrois", -"Vaux-Sûrois", -"Vaux-Sûroise", "Vaux-sur-Poligny", "Vaux-sur-Risle", "Vaux-sur-Saint-Urbain", @@ -25031,54 +16763,39 @@ FR_BASE_EXCEPTIONS = [ "Vaux-sur-Somme", "Vaux-sur-Sûre", "Vaux-sur-Vienne", -"Vaux-Villaine", "Vavray-le-Grand", "Vavray-le-Petit", "Vayres-sur-Essonne", "Vazeilles-Limandre", "Vazeilles-près-Saugues", -"veau-laq", -"veau-marin", "Veauville-lès-Baons", "Veauville-lès-Quelles", -"Védrines-Saint-Loup", -"végéto-sulfurique", "Veigy-Foncenex", "Velaine-en-Haye", "Velaine-sous-Amance", "Velars-sur-Ouche", -"velci-aller", "Velesmes-Echevanne", -"Velesmes-Échevanne", "Velesmes-Essarts", -"Vélez-Blanco", -"Vélez-Málaga", -"Vélez-Rubio", -"Vélizy-Villacoublay", +"Velesmes-Échevanne", +"Velle-le-Châtel", +"Velle-sur-Moselle", "Vellechevreux-et-Courbenans", "Vellefrey-et-Vellefrange", "Velleguindry-et-Levrecey", -"Velle-le-Châtel", -"Vellereille-les-Brayeux", "Vellereille-le-Sec", +"Vellereille-les-Brayeux", "Vellerot-lès-Belvoir", "Vellerot-lès-Vercel", -"Velle-sur-Moselle", "Vellexon-Queutey-et-Vaudey", "Vellexon-Queutrey-et-Vaudey", "Velloreille-lès-Choye", -"vélo-école", -"vélo-écoles", "Velone-Orneto", -"vélo-rail", -"vélo-rails", -"vélos-taxis", -"vélo-taxi", "Velotte-et-Tatignécourt", "Velsen-Noord", "Velsen-Zuid", "Veltem-Beisem", "Velzeke-Ruddershove", +"Ven-Zelderheide", "Venarey-les-Laumes", "Vendays-Montalivet", "Vendegies-au-Bois", @@ -25089,51 +16806,46 @@ FR_BASE_EXCEPTIONS = [ "Vendeuil-Caply", "Vendeuvre-du-Poitou", "Vendeuvre-sur-Barse", -"Vendin-lès-Béthune", "Vendin-le-Vieil", +"Vendin-lès-Béthune", "Vendredi-Saint", "Vendresse-Beaulne", "Vendresse-et-Troyon", "Veneux-les-Sablons", -"venez-y-voir", "Ventenac-Cabardès", "Ventenac-d'Aude", "Ventenac-en-Minervois", "Ventes-Saint-Rémy", -"ventre-madame", -"ventre-saint-gris", -"Ven-Zelderheide", +"Ver-lès-Chartres", +"Ver-sur-Launette", +"Ver-sur-Mer", "Verbano-Cusio-Ossola", "Vercel-Villedieu-le-Camp", "Verchain-Maugré", -"ver-coquin", "Verderel-lès-Sauqueuse", "Verdun-en-Lauragais", "Verdun-sur-Garonne", -"Verdun-sur-le-Doubs", "Verdun-sur-Meuse", -"Verel-de-Montbel", +"Verdun-sur-le-Doubs", "Verel-Pragondran", -"verge-d'or", +"Verel-de-Montbel", "Verger-sur-Dive", -"verges-d'or", "Vergt-de-Biron", -"Vérizet-Fleurville", -"Ver-lès-Chartres", "Verlhac-Tescou", "Vern-d'Anjou", +"Vern-sur-Seiche", "Verneil-le-Chétif", "Vernet-la-Varenne", "Vernet-les-Bains", +"Verneuil-Grand", +"Verneuil-Moustiers", +"Verneuil-Petit", "Verneuil-d'Avre-et-d'Iton", "Verneuil-en-Bourbonnais", "Verneuil-en-Halatte", -"Verneuil-Grand", -"Verneuil-le-Château", "Verneuil-l'Etang", "Verneuil-l'Étang", -"Verneuil-Moustiers", -"Verneuil-Petit", +"Verneuil-le-Château", "Verneuil-sous-Coucy", "Verneuil-sur-Avre", "Verneuil-sur-Igneraie", @@ -25154,106 +16866,40 @@ FR_BASE_EXCEPTIONS = [ "Vernoux-en-Gâtine", "Vernoux-en-Vivarais", "Vernoux-sur-Boutonne", -"Vern-sur-Seiche", -"Véronnes-les-Petites", "Verpillières-sur-Ource", "Verrens-Arvey", "Verreries-de-Moussans", "Verrey-sous-Drée", "Verrey-sous-Salmaise", +"Verrines-sous-Celles", "Verrières-de-Joux", "Verrières-du-Grosbois", "Verrières-en-Anjou", "Verrières-en-Forez", "Verrières-le-Buisson", -"Verrines-sous-Celles", -"Verseilles-le-Bas", -"Verseilles-le-Haut", -"Vers-en-Montagne", -"vers-librisme", -"vers-librismes", -"vers-libriste", -"vers-libristes", -"Versols-et-Lapeyre", "Vers-Pont-du-Gard", +"Vers-en-Montagne", "Vers-sous-Sellières", "Vers-sur-Méouge", "Vers-sur-Selles", -"Ver-sur-Launette", -"Ver-sur-Mer", -"vert-bois", -"vert-de-gris", -"vert-de-grisa", -"vert-de-grisai", -"vert-de-grisaient", -"vert-de-grisais", -"vert-de-grisait", -"vert-de-grisâmes", -"vert-de-grisant", -"vert-de-grisas", -"vert-de-grisasse", -"vert-de-grisassent", -"vert-de-grisasses", -"vert-de-grisassiez", -"vert-de-grisassions", -"vert-de-grisât", -"vert-de-grisâtes", -"vert-de-grise", -"vert-de-grisé", -"vert-de-grisée", -"vert-de-grisées", -"vert-de-grisent", -"vert-de-griser", -"vert-de-grisera", -"vert-de-griserai", -"vert-de-griseraient", -"vert-de-griserais", -"vert-de-griserait", -"vert-de-griseras", -"vert-de-grisèrent", -"vert-de-griserez", -"vert-de-griseriez", -"vert-de-griserions", -"vert-de-griserons", -"vert-de-griseront", -"vert-de-grises", -"vert-de-grisés", -"vert-de-grisez", -"vert-de-grisiez", -"vert-de-grisions", -"vert-de-grisons", -"Vert-en-Drouais", -"Verteuil-d'Agenais", -"Verteuil-sur-Charente", -"vert-jaune", -"Vert-le-Grand", -"Vert-le-Petit", -"vert-monnier", -"vert-monniers", +"Verseilles-le-Bas", +"Verseilles-le-Haut", +"Versols-et-Lapeyre", "Vert-Saint-Denis", "Vert-Toulon", +"Vert-en-Drouais", +"Vert-le-Grand", +"Vert-le-Petit", +"Verteuil-d'Agenais", +"Verteuil-sur-Charente", "Vesaignes-sous-Lafauche", "Vesaignes-sur-Marne", -"Vésenex-Crassy", -"Vésigneul-sur-Coole", -"Vésigneul-sur-Marne", "Vesles-et-Caumont", -"vesse-de-loup", -"vesses-de-loup", -"veston-cravate", -"vestons-cravates", "Vestric-et-Candiac", "Vesvres-sous-Chalancey", -"vétéro-testamentaire", -"vétéro-testamentaires", -"Vétraz-Monthoux", -"vetula-domussien", "Vetula-Domussien", -"vetula-domussienne", "Vetula-Domussienne", -"vetula-domussiennes", "Vetula-Domussiennes", -"vetula-domussiens", "Vetula-Domussiens", "Veuilly-la-Poterie", "Veules-les-Roses", @@ -25269,225 +16915,108 @@ FR_BASE_EXCEPTIONS = [ "Veyrines-de-Vergt", "Veyrins-Thuellin", "Vezels-Roussy", -"Vézeronce-Curtin", "Vezin-le-Coquet", -"Vézins-de-Lévézou", "Viala-du-Pas-de-Jaux", "Viala-du-Tarn", -"Viâpres-le-Grand", -"Viâpres-le-Petit", +"Vic-Fezensac", "Vic-de-Chassenay", "Vic-des-Prés", -"vice-amiral", -"vice-amirale", -"vice-amirales", -"vice-amirauté", -"vice-amiraux", -"vice-bailli", -"vice-baillis", -"vice-camérier", -"vice-cardinal", -"vice-champion", -"vice-championne", -"vice-championnes", -"vice-champions", -"vice-chancelier", -"vice-chanceliers", -"vice-consul", -"vice-consulat", -"vice-consulats", -"vice-consule", -"vice-directeur", -"vice-gérance", -"vice-gérances", -"vice-gérant", -"vice-gérants", -"vice-gérent", -"vice-gérents", -"vice-gouverneur", -"vice-légat", -"vice-légation", -"vice-légations", -"vice-légats", "Vic-en-Bigorre", "Vic-en-Carladais", -"vice-official", -"vice-préfet", -"vice-présida", -"vice-présidai", -"vice-présidaient", -"vice-présidais", -"vice-présidait", -"vice-présidâmes", -"vice-présidant", -"vice-présidas", -"vice-présidasse", -"vice-présidassent", -"vice-présidasses", -"vice-présidassiez", -"vice-présidassions", -"vice-présidât", -"vice-présidâtes", -"vice-préside", -"vice-présidé", -"vice-présidée", -"vice-présidées", -"vice-présidence", -"vice-présidences", -"vice-président", -"vice-présidente", -"vice-présidentes", -"vice-présidents", -"vice-présider", -"vice-présidera", -"vice-présiderai", -"vice-présideraient", -"vice-présiderais", -"vice-présiderait", -"vice-présideras", -"vice-présidèrent", -"vice-présiderez", -"vice-présideriez", -"vice-présiderions", -"vice-présiderons", -"vice-présideront", -"vice-présides", -"vice-présidés", -"vice-présidez", -"vice-présidiez", -"vice-présidions", -"vice-présidons", -"vice-procureur", -"vice-procureurs", -"vice-recteur", -"vice-recteurs", -"vice-rectrice", -"vice-rectrices", -"vice-reine", -"vice-reines", -"vice-roi", -"vice-rois", -"vice-royal", -"vice-royale", -"vice-royales", -"vice-royauté", -"vice-royautés", -"vice-royaux", -"vice-secrétaire", -"vice-sénéchal", -"vices-gouverneurs", -"vice-versa", -"Vic-Fezensac", -"Vichel-Nanteuil", "Vic-la-Gardiole", "Vic-le-Comte", "Vic-le-Fesq", -"Vicq-d'Auribat", -"Vicq-Exemplet", -"Vicq-sur-Breuilh", -"Vicq-sur-Gartempe", -"Vicq-sur-Mer", -"Vicq-sur-Nahon", "Vic-sous-Thil", "Vic-sur-Aisne", "Vic-sur-Cère", "Vic-sur-Seille", -"victim-blaming", +"Vichel-Nanteuil", +"Vicq-Exemplet", +"Vicq-d'Auribat", +"Vicq-sur-Breuilh", +"Vicq-sur-Gartempe", +"Vicq-sur-Mer", +"Vicq-sur-Nahon", "Victot-Pontfol", -"vide-atelier", -"vide-ateliers", -"vide-bouteille", -"vide-bouteilles", -"vide-cave", -"vide-caves", -"vide-citrons", -"vide-couilles", -"vide-dressing", -"vide-dressings", -"vide-gousset", -"vide-goussets", -"vide-grange", -"vide-grenier", -"vide-greniers", -"vide-maison", -"vide-maisons", -"vide-ordure", -"vide-ordures", -"vide-poche", -"vide-poches", -"vide-pomme", -"vide-pommes", -"vide-pommier", -"vide-vite", -"vieil-baugeois", "Vieil-Baugeois", -"vieil-baugeoise", "Vieil-Baugeoise", -"vieil-baugeoises", "Vieil-Baugeoises", "Vieil-Hesdin", -"vieil-hesdinois", "Vieil-Hesdinois", -"vieil-hesdinoise", "Vieil-Hesdinoise", -"vieil-hesdinoises", "Vieil-Hesdinoises", "Vieil-Moutier", +"Vieille-Brioude", +"Vieille-Chapelle", +"Vieille-Toulouse", +"Vieille-Église", +"Vieille-Église-en-Yvelines", +"Vieilles-Maisons-sur-Joudry", "Viel-Arcy", +"Viel-Mauricien", +"Viel-Mauricienne", +"Viel-Mauriciennes", +"Viel-Mauriciens", +"Viel-Saint-Remy", "Vielle-Adour", "Vielle-Aure", "Vielle-Louron", +"Vielle-Saint-Girons", +"Vielle-Soubiran", +"Vielle-Soubiranais", +"Vielle-Soubiranaise", +"Vielle-Soubiranaises", +"Vielle-Tursan", "Viellenave-d'Arthez", "Viellenave-de-Bidache", "Viellenave-de-Navarrenx", "Viellenave-sur-Bidouze", -"Vielle-Saint-Girons", -"Vielle-Soubiran", -"vielle-soubiranais", -"Vielle-Soubiranais", -"vielle-soubiranaise", -"Vielle-Soubiranaise", -"vielle-soubiranaises", -"Vielle-Soubiranaises", -"Vielle-Tursan", -"viel-mauricien", -"Viel-Mauricien", -"viel-mauricienne", -"Viel-Mauricienne", -"viel-mauriciennes", -"Viel-Mauriciennes", -"viel-mauriciens", -"Viel-Mauriciens", "Vielmur-sur-Agout", -"Viel-Saint-Remy", "Viels-Maisons", "Vienne-en-Arthies", "Vienne-en-Bessin", "Vienne-en-Val", "Vienne-la-Ville", "Vienne-le-Château", -"viens-poupoulerie", -"viens-poupouleries", "Vier-Bordes", "Viereth-Trunstadt", "Vierset-Barse", "Vierves-sur-Viroin", "Vierville-sur-Mer", "Viet-Nam", -"Viêt-nam", "Vieu-d'Izenave", -"Viéville-en-Haye", -"Viéville-sous-les-Côtes", +"Vieux-Berquin", +"Vieux-Boucau-les-Bains", +"Vieux-Bourg", +"Vieux-Champagne", +"Vieux-Charmont", +"Vieux-Château", +"Vieux-Condé", +"Vieux-Ferrette", +"Vieux-Fort", +"Vieux-Fumé", +"Vieux-Habitants", +"Vieux-Lixheim", +"Vieux-Manoir", +"Vieux-Mareuil", +"Vieux-Mesnil", +"Vieux-Moulin", +"Vieux-Pont", +"Vieux-Pont-en-Auge", +"Vieux-Port", +"Vieux-Reng", +"Vieux-Rouen-sur-Bresle", +"Vieux-Ruffec", +"Vieux-Thann", +"Vieux-Viel", +"Vieux-Vy-sur-Couesnon", +"Vieux-lès-Asfeld", "Vievy-le-Rayé", -"vif-argent", -"vif-gage", -"vigne-blanche", -"vignes-blanches", "Vignes-la-Côte", -"Vigneulles-lès-Hattonchâtel", "Vigneul-sous-Montmédy", -"Vigneux-de-Bretagne", +"Vigneulles-lès-Hattonchâtel", "Vigneux-Hocquet", +"Vigneux-de-Bretagne", "Vigneux-sur-Seine", "Vignola-Falesina", "Vignoux-sous-les-Aix", @@ -25503,11 +17032,6 @@ FR_BASE_EXCEPTIONS = [ "Vildé-Guingalan", "Villabona-Amasa", "Village-Neuf", -"village-rue", -"villages-rue", -"villages-rues", -"villages-tas", -"village-tas", "Villaines-en-Duesmois", "Villaines-la-Carelle", "Villaines-la-Gonais", @@ -25517,19 +17041,12 @@ FR_BASE_EXCEPTIONS = [ "Villaines-sous-Bois", "Villaines-sous-Lucé", "Villaines-sous-Malicorne", +"Villar-Loubière", +"Villar-Saint-Anselme", +"Villar-Saint-Pancrace", "Villar-d'Arêne", +"Villar-en-Val", "Villard-Bonnot", -"villard-de-lans", -"Villard-de-Lans", -"villard-d'hérien", -"Villard-d'Hérien", -"villard-d'hérienne", -"Villard-d'Hérienne", -"villard-d'hériennes", -"Villard-d'Hériennes", -"villard-d'hériens", -"Villard-d'Hériens", -"Villard-d'Héry", "Villard-Léger", "Villard-Notre-Dame", "Villard-Reculas", @@ -25537,47 +17054,83 @@ FR_BASE_EXCEPTIONS = [ "Villard-Saint-Christophe", "Villard-Saint-Sauveur", "Villard-Sallet", -"Villards-d'Héria", +"Villard-d'Hérien", +"Villard-d'Hérienne", +"Villard-d'Hériennes", +"Villard-d'Hériens", +"Villard-d'Héry", +"Villard-de-Lans", "Villard-sur-Bienne", "Villard-sur-Doron", "Villard-sur-l'Ain", +"Villards-d'Héria", "Villarejo-Periesteban", -"Villar-en-Val", -"Villar-Loubière", "Villarodin-Bourget", -"Villar-Saint-Anselme", -"Villar-Saint-Pancrace", "Villars-Brandis", "Villars-Colmars", -"Villarsel-sur-Marly", -"Villars-en-Azois", -"Villars-en-Pons", -"Villars-Épeney", -"Villars-et-Villenotte", "Villars-Fontaine", -"Villars-le-Comte", -"Villars-le-Pautel", -"Villars-lès-Blamont", -"Villars-les-Bois", -"Villars-les-Dombes", -"Villars-le-Sec", -"Villars-les-Moines", -"Villars-le-Terroir", -"Villars-Sainte-Croix", "Villars-Saint-Georges", "Villars-Saint-Marcellin", +"Villars-Sainte-Croix", "Villars-Santenoge", +"Villars-en-Azois", +"Villars-en-Pons", +"Villars-et-Villenotte", +"Villars-le-Comte", +"Villars-le-Pautel", +"Villars-le-Sec", +"Villars-le-Terroir", +"Villars-les-Bois", +"Villars-les-Dombes", +"Villars-les-Moines", +"Villars-lès-Blamont", "Villars-sous-Dampjoux", "Villars-sous-Ecot", -"Villars-sous-Écot", "Villars-sous-Yens", +"Villars-sous-Écot", "Villars-sur-Glâne", "Villars-sur-Var", +"Villars-Épeney", +"Villarsel-sur-Marly", "Villarta-Quintana", "Villarzel-Cabardès", "Villarzel-du-Razès", "Villaverde-Mogina", "Villaz-Saint-Pierre", +"Ville-Dommange", +"Ville-Houdlémont", +"Ville-Langy", +"Ville-Saint-Jacques", +"Ville-Savoye", +"Ville-au-Montois", +"Ville-au-Val", +"Ville-d'Avray", +"Ville-devant-Belrain", +"Ville-devant-Chaumont", +"Ville-di-Paraso", +"Ville-di-Pietrabugno", +"Ville-du-Pont", +"Ville-en-Blaisois", +"Ville-en-Sallaz", +"Ville-en-Selve", +"Ville-en-Tardenois", +"Ville-en-Vermois", +"Ville-en-Woëvre", +"Ville-la-Grand", +"Ville-le-Marclet", +"Ville-sous-Anjou", +"Ville-sous-la-Ferté", +"Ville-sur-Ancre", +"Ville-sur-Arce", +"Ville-sur-Cousances", +"Ville-sur-Illon", +"Ville-sur-Jarnioux", +"Ville-sur-Lumes", +"Ville-sur-Retourne", +"Ville-sur-Saulx", +"Ville-sur-Terre", +"Ville-sur-Tourbe", +"Ville-sur-Yron", "Villebois-Lavalette", "Villebois-les-Pins", "Villebon-sur-Yvette", @@ -25586,9 +17139,9 @@ FR_BASE_EXCEPTIONS = [ "Villedieu-la-Blouère", "Villedieu-le-Camp", "Villedieu-le-Château", -"Villedieu-lès-Bailleul", "Villedieu-les-Poêles", "Villedieu-les-Poêles-Rouffigny", +"Villedieu-lès-Bailleul", "Villedieu-sur-Indre", "Villefranche-d'Albigeois", "Villefranche-d'Allier", @@ -25617,103 +17170,280 @@ FR_BASE_EXCEPTIONS = [ "Villemur-sur-Tarn", "Villenauxe-la-Grande", "Villenauxe-la-Petite", -"Villenave-de-Rions", "Villenave-d'Ornon", +"Villenave-de-Rions", "Villenave-près-Béarn", "Villenave-près-Marsac", +"Villeneuve-Frouville", +"Villeneuve-Loubet", +"Villeneuve-Lécussan", +"Villeneuve-Minervois", +"Villeneuve-Renneville-Chevigny", +"Villeneuve-Saint-Denis", +"Villeneuve-Saint-Georges", +"Villeneuve-Saint-Germain", +"Villeneuve-Saint-Salves", +"Villeneuve-Saint-Vistre-et-Villevotte", +"Villeneuve-Tolosane", +"Villeneuve-au-Chemin", +"Villeneuve-d'Allier", +"Villeneuve-d'Amont", +"Villeneuve-d'Ascq", +"Villeneuve-d'Aval", +"Villeneuve-d'Entraunes", +"Villeneuve-d'Olmes", +"Villeneuve-de-Berg", +"Villeneuve-de-Duras", +"Villeneuve-de-Marc", +"Villeneuve-de-Marsan", +"Villeneuve-de-Rivière", +"Villeneuve-de-la-Raho", +"Villeneuve-du-Latou", +"Villeneuve-du-Paréage", +"Villeneuve-en-Montagne", +"Villeneuve-en-Perseigne", +"Villeneuve-en-Retz", +"Villeneuve-l'Archevêque", +"Villeneuve-la-Comptal", +"Villeneuve-la-Comtesse", +"Villeneuve-la-Dondagre", +"Villeneuve-la-Garenne", +"Villeneuve-la-Guyard", +"Villeneuve-la-Lionne", +"Villeneuve-la-Rivière", +"Villeneuve-le-Comte", +"Villeneuve-le-Roi", +"Villeneuve-les-Bordes", +"Villeneuve-les-Cerfs", +"Villeneuve-les-Corbières", +"Villeneuve-les-Genêts", +"Villeneuve-les-Sablons", +"Villeneuve-lès-Avignon", +"Villeneuve-lès-Bouloc", +"Villeneuve-lès-Béziers", +"Villeneuve-lès-Charnod", +"Villeneuve-lès-Lavaur", +"Villeneuve-lès-Maguelone", +"Villeneuve-lès-Montréal", +"Villeneuve-sous-Charigny", +"Villeneuve-sous-Dammartin", +"Villeneuve-sous-Pymont", +"Villeneuve-sur-Allier", +"Villeneuve-sur-Auvers", +"Villeneuve-sur-Bellot", +"Villeneuve-sur-Cher", +"Villeneuve-sur-Conie", +"Villeneuve-sur-Fère", +"Villeneuve-sur-Lot", +"Villeneuve-sur-Verberie", +"Villeneuve-sur-Vère", +"Villeneuve-sur-Yonne", "Villennes-sur-Seine", "Villequier-Aumont", "Villerouge-Termenès", "Villeroy-sur-Méholle", -"villes-champignons", -"villes-clés", -"Villesèque-des-Corbières", -"villes-États", -"villes-provinces", +"Villers-Agron-Aiguizy", +"Villers-Allerand", +"Villers-Bocage", +"Villers-Bouton", +"Villers-Bretonneux", +"Villers-Brûlin", +"Villers-Buzon", +"Villers-Campsart", +"Villers-Canivet", +"Villers-Carbonnel", +"Villers-Cernay", +"Villers-Chemin-et-Mont-lès-Étrelles", +"Villers-Chief", +"Villers-Châtel", +"Villers-Cotterêts", +"Villers-Farlay", +"Villers-Faucon", +"Villers-Franqueux", +"Villers-Grélot", +"Villers-Guislain", +"Villers-Hélon", +"Villers-Marmery", +"Villers-Outréaux", +"Villers-Pater", +"Villers-Patras", +"Villers-Plouich", +"Villers-Pol", +"Villers-Robert", +"Villers-Rotin", +"Villers-Saint-Barthélemy", +"Villers-Saint-Christophe", +"Villers-Saint-Frambourg", +"Villers-Saint-Genest", +"Villers-Saint-Martin", +"Villers-Saint-Paul", +"Villers-Saint-Sépulcre", +"Villers-Semeuse", +"Villers-Sir-Simon", +"Villers-Sire-Nicole", +"Villers-Stoncourt", +"Villers-Tournelle", +"Villers-Vaudey", +"Villers-Vermont", +"Villers-Vicomte", +"Villers-au-Bois", +"Villers-au-Flos", +"Villers-au-Tertre", +"Villers-aux-Bois", +"Villers-aux-Nœuds", +"Villers-aux-Vents", +"Villers-aux-Érables", +"Villers-devant-Dun", +"Villers-devant-Mouzon", +"Villers-devant-le-Thour", +"Villers-en-Argonne", +"Villers-en-Arthies", +"Villers-en-Cauchies", +"Villers-en-Haye", +"Villers-en-Vexin", +"Villers-l'Hôpital", +"Villers-la-Chèvre", +"Villers-la-Combe", +"Villers-la-Faye", +"Villers-la-Montagne", +"Villers-la-Ville", +"Villers-le-Château", +"Villers-le-Lac", +"Villers-le-Rond", +"Villers-le-Sec", +"Villers-le-Tilleul", +"Villers-le-Tourneur", +"Villers-les-Bois", +"Villers-les-Pots", +"Villers-lès-Cagnicourt", +"Villers-lès-Guise", +"Villers-lès-Luxeuil", +"Villers-lès-Mangiennes", +"Villers-lès-Moivrons", +"Villers-lès-Nancy", +"Villers-lès-Roye", +"Villers-sous-Ailly", +"Villers-sous-Chalamont", +"Villers-sous-Châtillon", +"Villers-sous-Foucarmont", +"Villers-sous-Montrond", +"Villers-sous-Pareid", +"Villers-sous-Prény", +"Villers-sous-Saint-Leu", +"Villers-sur-Auchy", +"Villers-sur-Authie", +"Villers-sur-Bar", +"Villers-sur-Bonnières", +"Villers-sur-Coudun", +"Villers-sur-Fère", +"Villers-sur-Mer", +"Villers-sur-Meuse", +"Villers-sur-Nied", +"Villers-sur-Port", +"Villers-sur-Saulnot", +"Villers-sur-Trie", +"Villers-sur-le-Mont", +"Villers-sur-le-Roule", +"Villers-Écalles", "Villes-sur-Auzon", -"Villey-le-Sec", +"Villesèque-des-Corbières", +"Villette-d'Anthon", +"Villette-de-Vienne", +"Villette-lès-Arbois", +"Villette-lès-Dole", +"Villette-sur-Ain", +"Villette-sur-Aube", "Villey-Saint-Etienne", "Villey-Saint-Étienne", +"Villey-le-Sec", "Villey-sur-Tille", "Villez-sous-Bailleul", "Villez-sur-le-Neubourg", -"Villié-Morgon", +"Villiers-Adam", +"Villiers-Charlemagne", +"Villiers-Couture", +"Villiers-Fossard", +"Villiers-Herbisse", +"Villiers-Louis", +"Villiers-Saint-Benoît", +"Villiers-Saint-Denis", +"Villiers-Saint-Frédéric", +"Villiers-Saint-Georges", +"Villiers-Saint-Orien", +"Villiers-Vineux", +"Villiers-au-Bouin", +"Villiers-aux-Corneilles", +"Villiers-en-Bière", +"Villiers-en-Bois", +"Villiers-en-Désœuvre", +"Villiers-en-Lieu", +"Villiers-en-Morvan", +"Villiers-en-Plaine", +"Villiers-le-Bel", +"Villiers-le-Bois", +"Villiers-le-Bâcle", +"Villiers-le-Duc", +"Villiers-le-Mahieu", +"Villiers-le-Morhier", +"Villiers-le-Pré", +"Villiers-le-Roux", +"Villiers-le-Sec", +"Villiers-les-Hauts", +"Villiers-lès-Aprey", +"Villiers-sous-Grez", +"Villiers-sous-Mortagne", +"Villiers-sous-Praslin", +"Villiers-sur-Chizé", +"Villiers-sur-Loir", +"Villiers-sur-Marne", +"Villiers-sur-Morin", +"Villiers-sur-Orge", +"Villiers-sur-Seine", +"Villiers-sur-Suize", +"Villiers-sur-Tholon", +"Villiers-sur-Yonne", "Villieu-Loyes-Mollon", "Villingen-Schwenningen", +"Villié-Morgon", "Villons-les-Buissons", -"Villotte-devant-Louppy", "Villotte-Saint-Seine", +"Villotte-devant-Louppy", "Villotte-sur-Aire", "Villotte-sur-Ource", +"Villy-Bocage", +"Villy-en-Auxois", +"Villy-en-Trodes", +"Villy-le-Bois", +"Villy-le-Bouveret", +"Villy-le-Maréchal", +"Villy-le-Moutier", +"Villy-le-Pelloux", +"Villy-lez-Falaise", +"Villy-sur-Yères", "Vilosnes-Haraumont", "Vilters-Wangs", "Vincent-Froideville", +"Vincy-Manœuvre", "Vincy-Manœuvre", "Vincy-Reuil-et-Magny", "Vindrac-Alayrac", "Vineuil-Saint-Firmin", -"vingt-cinq", "Vingt-Cinq", -"vingt-cinquième", -"vingt-cinquièmes", -"vingt-deux", -"vingt-deuxain", -"vingt-deuxains", -"vingt-deuxième", -"vingt-deuxièmes", -"vingt-et-un", -"vingt-et-une", -"vingt-et-unième", -"vingt-et-unièmes", "Vingt-Hanaps", -"vingt-hanapsien", "Vingt-Hanapsien", -"vingt-hanapsienne", "Vingt-Hanapsienne", -"vingt-hanapsiennes", "Vingt-Hanapsiennes", -"vingt-hanapsiens", "Vingt-Hanapsiens", -"vingt-huit", "Vingt-Huit", -"vingt-huitième", -"vingt-huitièmes", -"vingt-neuf", -"vingt-neuvième", -"vingt-neuvièmes", -"vingt-quatrain", -"vingt-quatrains", -"vingt-quatre", -"vingt-quatrième", -"vingt-quatrièmes", -"vingt-sept", "Vingt-Sept", -"vingt-septième", -"vingt-septièmes", -"vingt-six", -"vingt-sixain", -"vingt-sixième", -"vingt-sixièmes", -"vingt-trois", -"vingt-troisième", -"vingt-troisièmes", -"vino-benzoïque", -"vino-benzoïques", "Vinon-sur-Verdon", "Vins-sur-Caramy", "Viodos-Abense-de-Bas", -"violet-évêque", "Viols-en-Laval", "Viols-le-Fort", -"viornes-tin", -"viorne-tin", -"vire-capot", -"vire-capots", -"Viré-en-Champagne", "Vire-sur-Lot", "Vireux-Molhain", "Vireux-Wallerand", -"vire-vire", "Virey-le-Grand", "Virey-sous-Bar", "Virginal-Samme", @@ -25722,25 +17452,16 @@ FR_BASE_EXCEPTIONS = [ "Virieu-le-Petit", "Viry-Châtillon", "Viry-Noureuil", -"visa-bourgien", -"Visa-Bourgien", -"visa-bourgienne", -"Visa-Bourgienne", -"visa-bourgiennes", -"Visa-Bourgiennes", -"visa-bourgiens", -"Visa-Bourgiens", -"vis-à-vis", +"Viré-en-Champagne", "Vis-en-Artois", +"Visa-Bourgien", +"Visa-Bourgienne", +"Visa-Bourgiennes", +"Visa-Bourgiens", "Vissac-Auteyrac", -"visuo-spacial", -"visuo-spaciale", -"visuo-spaciales", -"visuo-spaciaux", -"vit-de-mulet", "Vitoria-Gasteiz", -"Vitrac-en-Viadène", "Vitrac-Saint-Vincent", +"Vitrac-en-Viadène", "Vitrac-sur-Montane", "Vitrai-sous-Laigle", "Vitray-en-Beauce", @@ -25748,12 +17469,12 @@ FR_BASE_EXCEPTIONS = [ "Vitrey-sur-Mance", "Vitrolles-en-Luberon", "Vitrolles-en-Lubéron", +"Vitry-Laché", "Vitry-aux-Loges", "Vitry-en-Artois", "Vitry-en-Charollais", "Vitry-en-Montagne", "Vitry-en-Perthois", -"Vitry-Laché", "Vitry-la-Ville", "Vitry-le-Croisé", "Vitry-le-François", @@ -25766,15 +17487,10 @@ FR_BASE_EXCEPTIONS = [ "Vitz-sur-Authie", "Viuz-en-Sallaz", "Viuz-la-Chiésaz", -"vivaro-alpin", -"vivaro-alpins", -"vive-eau", -"vive-la-joie", "Vive-Saint-Bavon", "Vive-Saint-Éloi", -"vives-eaux", -"Vivier-au-Court", "Vivier-Danger", +"Vivier-au-Court", "Viviers-du-Lac", "Viviers-le-Gras", "Viviers-lès-Lavaur", @@ -25782,58 +17498,24 @@ FR_BASE_EXCEPTIONS = [ "Viviers-lès-Offroicourt", "Viviers-sur-Artaut", "Viviers-sur-Chiers", -"vivre-ensemble", -"v'là", +"Viâpres-le-Grand", +"Viâpres-le-Petit", +"Viéville-en-Haye", +"Viéville-sous-les-Côtes", +"Viêt-nam", "Vlaardinger-Ambacht", "Vlagtwedder-Barlage", "Vlagtwedder-Veldhuis", "Vlodrop-Station", -"v'nir", -"v'nu", -"Vœlfling-lès-Bouzonville", -"Vœuil-et-Giget", "Vogelsang-Warsin", "Void-Vacon", -"voile-manteau", -"voile-manteaux", "Voisins-le-Bretonneux", -"vois-tu", -"voiture-bar", -"voiture-bélier", -"voiture-cage", -"voiture-couchettes", -"voiture-lits", -"voiture-pilote", -"voiture-restaurant", -"voiture-salon", -"voitures-balais", -"voitures-bars", -"voitures-béliers", -"voitures-cages", -"voitures-couchettes", -"voitures-lits", -"voitures-pilotes", -"voitures-restaurants", -"voitures-salons", -"voitures-ventouses", -"voiture-ventouse", "Voivres-lès-le-Mans", -"vol-au-vent", -"vol-bélier", -"vol-béliers", -"volley-ball", -"volley-balls", "Vollore-Montagne", "Vollore-Ville", -"Volmerange-lès-Boulay", "Volmerange-les-Mines", -"volt-ampère", -"volt-ampères", -"volte-face", -"volte-faces", +"Volmerange-lès-Boulay", "Vomécourt-sur-Madon", -"vomito-negro", -"vomito-négro", "Voor-Drempt", "Voray-sur-l'Ognon", "Vorges-les-Pins", @@ -25845,118 +17527,86 @@ FR_BASE_EXCEPTIONS = [ "Voulaines-les-Templiers", "Vouneuil-sous-Biard", "Vouneuil-sur-Vienne", -"vous-même", -"vous-mêmes", "Voutenay-sur-Cure", "Vouthon-Bas", "Vouthon-Haut", "Vouvray-sur-Huisne", "Vouvray-sur-Loir", "Vovray-en-Bornes", -"voyageur-kilomètre", -"voyageurs-kilomètres", -"voyez-vous", "Vraignes-en-Vermandois", "Vraignes-lès-Hornoy", "Vresse-sur-Semois", -"Vrigne-aux-Bois", "Vrigne-Meuse", -"vrigne-meusien", "Vrigne-Meusien", -"vrigne-meusienne", "Vrigne-Meusienne", -"vrigne-meusiennes", "Vrigne-Meusiennes", -"vrigne-meusiens", "Vrigne-Meusiens", +"Vrigne-aux-Bois", "Vrijhoeve-Capelle", "Vroncourt-la-Côte", -"v's", -"vu-arriver", "Vufflens-la-Ville", "Vufflens-le-Château", "Vuisternens-devant-Romont", "Vuisternens-en-Ogoz", "Vulaines-lès-Provins", "Vulaines-sur-Seine", -"Vyans-le-Val", -"Vyle-et-Tharoul", "Vy-le-Ferroux", +"Vy-les-Luron", +"Vy-les-Lurone", +"Vy-les-Lurones", +"Vy-les-Lurons", "Vy-lès-Filain", "Vy-lès-Lure", -"vy-les-luron", -"Vy-les-Luron", -"vy-les-lurone", -"Vy-les-Lurone", -"vy-les-lurones", -"Vy-les-Lurones", -"vy-les-lurons", -"Vy-les-Lurons", "Vy-lès-Rupt", +"Vyans-le-Val", +"Vyle-et-Tharoul", "Vyt-lès-Belvoir", +"Vœlfling-lès-Bouzonville", +"Vœuil-et-Giget", +"Védrines-Saint-Loup", +"Vélez-Blanco", +"Vélez-Málaga", +"Vélez-Rubio", +"Vélizy-Villacoublay", +"Vérizet-Fleurville", +"Véronnes-les-Petites", +"Vésenex-Crassy", +"Vésigneul-sur-Coole", +"Vésigneul-sur-Marne", +"Vétraz-Monthoux", +"Vézeronce-Curtin", +"Vézins-de-Lévézou", +"Vœlfling-lès-Bouzonville", +"Vœuil-et-Giget", "Wadonville-en-Woëvre", "Wageningen-Hoog", -"wagon-bar", -"wagon-citerne", -"wagon-couchette", -"wagon-couchettes", -"wagon-foudre", -"wagon-grue", -"wagon-lit", -"wagon-lits", -"wagon-poche", -"wagon-poste", -"wagon-réservoir", -"wagon-restaurant", -"wagon-salon", -"wagons-bars", -"wagons-citernes", -"wagons-couchettes", -"wagons-foudres", -"wagons-grues", -"wagons-lits", -"wagons-réservoirs", -"wagons-restaurants", -"wagons-salons", -"wagons-tombereaux", -"wagons-trémie", -"wagon-tombereau", -"wagon-trémie", -"wagon-vanne", -"wah-wah", "Wailly-Beaucamp", +"Wald-Michelbach", "Waldeck-Frankenberg", "Waldfischbach-Burgalben", "Waldhof-Falkenstein", -"Wald-Michelbach", "Waldshut-Tiengen", "Walhain-Saint-Paul", "Walincourt-Selvigny", -"walkies-talkies", -"walkie-talkie", "Wallendorf-Pont", -"Wallers-en-Fagne", "Wallers-Trélon", +"Wallers-en-Fagne", "Wallis-et-Futuna", "Wallon-Cappel", -"wallon-cappelois", "Wallon-Cappelois", -"wallon-cappeloise", "Wallon-Cappeloise", -"wallon-cappeloises", "Wallon-Cappeloises", "Waltenheim-sur-Zorn", "Walton-on-Thames", "Wanchy-Capval", "Wandignies-Hamage", "Wanfercée-Baulet", -"Wangenbourg-Engenthal", "Wangen-Brüttisellen", +"Wangenbourg-Engenthal", "Wannegem-Lede", "Wanzleben-Börde", -"waray-waray", -"Waret-la-Chaussée", "Waret-l'Évêque", +"Waret-la-Chaussée", "Warfusée-Abancourt", "Wargemoulin-Hurlus", "Wargnies-le-Grand", @@ -25971,49 +17621,31 @@ FR_BASE_EXCEPTIONS = [ "Wasmes-Audemez-Briffœil", "Wasnes-au-Bac", "Wassy-sur-Blaise", -"water-ballast", -"water-ballasts", -"water-closet", -"water-closets", "Waterland-Oudeman", "Watermael-Boitsfort", -"water-polo", -"water-proof", -"water-proofs", "Wath-on-Dearne", "Wath-upon-Dearne", "Wattignies-la-Victoire", "Wauthier-Braine", -"wauthier-brainois", "Wauthier-Brainois", "Wauthier-Brainoise", -"waux-hall", -"waux-halls", -"Wavrans-sur-l'Aa", "Wavrans-sur-Ternoise", -"Wavrechain-sous-Denain", -"Wavrechain-sous-Faulx", +"Wavrans-sur-l'Aa", "Wavre-Notre-Dame", "Wavre-Saint-Catherine", "Wavre-Sainte-Catherine", -"waza-ari", -"w.-c.", -"web-to-print", -"week-end", -"week-ends", -"Weiler-la-Tour", +"Wavrechain-sous-Denain", +"Wavrechain-sous-Faulx", "Weiler-Simmerberg", +"Weiler-la-Tour", "Weilheim-Schongau", "Weimar-Campagne", "Weißenborn-Lüderode", "Weißenburg-Gunzenhausen", "Welles-Pérennes", "Wemaers-Cappel", -"wemaers-cappelois", "Wemaers-Cappelois", -"wemaers-cappeloise", "Wemaers-Cappeloise", -"wemaers-cappeloises", "Wemaers-Cappeloises", "Wenningstedt-Braderup", "Wenum-Wiesel", @@ -26022,105 +17654,13453 @@ FR_BASE_EXCEPTIONS = [ "Wervicq-Nord", "Wervicq-Sud", "Wesembeek-Ophem", -"wesh-wesh", "West-Barendrecht", "West-Cappel", -"west-cappelois", "West-Cappelois", -"west-cappeloise", "West-Cappeloise", -"west-cappeloises", "West-Cappeloises", -"Westerhaar-Vriezenveensewijk", -"Wester-Koggenland", -"Wester-Ohrstedt", "West-Graftdijk", -"Westhouse-Marmoutier", -"Westkapelle-Binnen", "West-Knollendam", -"Westrem-Saint-Denis", "West-Souburg", "West-Terschelling", +"Wester-Koggenland", +"Wester-Ohrstedt", +"Westerhaar-Vriezenveensewijk", +"Westhouse-Marmoutier", +"Westkapelle-Binnen", +"Westrem-Saint-Denis", "Wettin-Löbejün", -"Wezembeek-Oppem", "Wez-Velvain", -"white-spirit", +"Wezembeek-Oppem", +"Wi-Fi", "Wickersheim-Wilshausen", -"Wiège-Faty", "Wiencourt-l'Equipée", "Wiencourt-l'Équipée", -"Wierre-au-Bois", "Wierre-Effroy", -"Wi-Fi", +"Wierre-au-Bois", "Wihr-au-Val", "Wihr-en-Plaine", "Wilkau-Haßlau", "Willer-sur-Thur", -"willy-willy", "Wilp-Achterhoek", "Wilzenberg-Hußweiler", "Wingen-sur-Moder", "Winghe-Saint-Georges", -"Winkel-Sainte-Croix", "Winkel-Saint-Éloi", +"Winkel-Sainte-Croix", "Wintzenheim-Kochersberg", "Wiry-au-Mont", "Witry-lès-Reims", -"witsuwit'en", -"Wœlfling-lès-Sarreguemines", +"Wiège-Faty", "Wokuhl-Dabelow", "Wolframs-Eschenbach", "Wolfsburg-Unkeroda", -"Woluwe-Saint-Étienne", "Woluwe-Saint-Lambert", "Woluwe-Saint-Pierre", +"Woluwe-Saint-Étienne", "Wormeldange-Haut", "Wortegem-Petegem", -"wuchiaping'ien", "Wuchiaping'ien", -"Wünnewil-Flamatt", "Wust-Fischbeck", "Wutha-Farnroda", "Wy-dit-Joli-Village", -"Xanton-Chassenon", +"Wœlfling-lès-Sarreguemines", +"Wünnewil-Flamatt", +"Wœlfling-lès-Sarreguemines", +"X-SAMPA", "X-arbre", "X-arbres", "X-board", "X-boards", +"Xanton-Chassenon", "Xivray-et-Marvoisin", "Xivry-Circourt", "Xonrupt-Longemer", -"X-SAMPA", -"y'a", -"yacht-club", -"yacht-clubs", "Yaucourt-Bussus", -"Yécora-Iekora", "Yernée-Fraineux", +"Ygos-Saint-Saturnin", +"Yo-kai", +"Yorkshire-et-Humber", +"Ypreville-Biville", +"Yronde-et-Buron", +"Yssac-la-Tourette", +"Yverdon-les-Bains", +"Yves-Gomezée", +"Yvetot-Bocage", +"Yvignac-la-Tour", +"Yville-sur-Seine", +"Yvoy-le-Marron", +"Yvrac-et-Malleyrand", +"Yvré-l'Evêque", +"Yvré-l'Évêque", +"Yvré-le-Pôlin", +"Yzeures-sur-Creuse", "Yèvre-la-Ville", "Yèvre-le-Châtel", "Yèvres-le-Petit", -"yé-yé", -"Ygos-Saint-Saturnin", +"Yécora-Iekora", +"Z-grille", +"Z-grilles", +"Z/E-8-DDA", +"Z9-12:Ac", +"Z9-dodécénylacétate", +"Zahna-Elster", +"Zella-Mehlis", +"Zeltingen-Rachtig", +"Zend-avesta", +"Zernitz-Lohm", +"Zeulenroda-Triebes", +"Zevenhuizen-Moerkapelle", +"Zichen-Zussen-Bolder", +"Ziegra-Knobelsdorf", +"Zihlschlacht-Sitterdorf", +"Zillis-Reischen", +"Ziortza-Bolibar", +"Zoerle-Parwijs", +"Zoeterwoude-Dorp", +"Zoeterwoude-Rijndijk", +"Zschaitz-Ottewig", +"Zuid-Beijerland", +"Zuid-Eierland", +"Zuid-Polsbroek", +"Zuid-Scharwoude", +"Zuid-Spierdijk", +"Zuid-Waddinxveen", +"Zwaagdijk-Oost", +"Zwaagdijk-West", +"Zétrud-Lumay", +"a-sexualisa", +"a-sexualisai", +"a-sexualisaient", +"a-sexualisais", +"a-sexualisait", +"a-sexualisant", +"a-sexualisas", +"a-sexualisasse", +"a-sexualisassent", +"a-sexualisasses", +"a-sexualisassiez", +"a-sexualisassions", +"a-sexualise", +"a-sexualisent", +"a-sexualiser", +"a-sexualisera", +"a-sexualiserai", +"a-sexualiseraient", +"a-sexualiserais", +"a-sexualiserait", +"a-sexualiseras", +"a-sexualiserez", +"a-sexualiseriez", +"a-sexualiserions", +"a-sexualiserons", +"a-sexualiseront", +"a-sexualises", +"a-sexualisez", +"a-sexualisiez", +"a-sexualisions", +"a-sexualisons", +"a-sexualisâmes", +"a-sexualisât", +"a-sexualisâtes", +"a-sexualisèrent", +"a-sexualisé", +"a-sexualisée", +"a-sexualisées", +"a-sexualisés", +"abaisse-langue", +"abaisse-langues", +"abou-hannès", +"abou-mengel", +"abou-mengels", +"abri-sous-roche", +"abri-vent", +"abricot-pêche", +"abricotier-pays", +"abricots-pêches", +"abris-sous-roche", +"abris-vent", +"absorbeur-neutralisateur", +"acajou-amer", +"acajou-bois", +"acajous-amers", +"acajous-bois", +"accord-cadre", +"accords-cadres", +"accroche-coeur", +"accroche-coeurs", +"accroche-cœur", +"accroche-cœurs", +"accroche-pied", +"accroche-pieds", +"accroche-plat", +"accroche-plats", +"achard-bourgeois", +"achard-bourgeoise", +"achard-bourgeoises", +"acibenzolar-S-méthyle", +"acide-N-1-naphtyl-phtalamique", +"acide-phénol", +"acides-phénols", +"acido-alcalimétrie", +"acido-alcoolo-résistance", +"acido-alcoolo-résistances", +"acido-alcoolo-résistant", +"acido-alcoolo-résistante", +"acido-alcoolo-résistantes", +"acido-alcoolo-résistants", +"acido-basique", +"acido-résistant", +"acido-résistants", +"acqua-toffana", +"acqua-toffanas", +"acquae-sextien", +"acquae-sextienne", +"acquae-sextiennes", +"acquae-sextiens", +"acquit-patent", +"acquit-à-caution", +"acquits-patents", +"acquits-à-caution", +"acting-out", +"actino-uranium", +"acétyl-salicylate", +"acétyl-salicylates", +"add-on", +"adieu-mes-couilles", +"adieu-tout", +"adieu-touts", +"adieu-va", +"adieu-vas", +"adieu-vat", +"adieu-vats", +"adiposo-génital", +"adiposo-génitale", +"adiposo-génitales", +"adiposo-génitaux", +"adjudant-chef", +"adjudants-chefs", +"africain-américain", +"africaine-américaine", +"africaines-américaines", +"africains-américains", +"africano-brésilien", +"africano-brésilienne", +"africano-brésiliennes", +"africano-brésiliens", +"africano-taïwanais", +"africano-taïwanaise", +"africano-taïwanaises", +"agace-pissette", +"agar-agar", +"agasse-tambourinette", +"agatha-christien", +"agit-prop", +"agnus-castus", +"agnus-dei", +"agora-phobie", +"agora-phobies", +"ai-cham", +"aide-comptable", +"aide-mémoire", +"aide-mémoires", +"aide-soignant", +"aide-soignante", +"aide-soignantes", +"aide-soignants", +"aide-écuyer", +"aide-écuyers", +"aide-éducateur", +"aides-soignantes", +"aides-soignants", +"aigle-bar", +"aigre-douce", +"aigre-doux", +"aigre-moines", +"aigres-douces", +"aigres-doux", +"aigue-marine", +"aigue-marines", +"aigues-juntais", +"aigues-juntaise", +"aigues-juntaises", +"aigues-marines", +"aigues-mortais", +"aigues-mortaise", +"aigues-mortaises", +"aigues-vivesien", +"aigues-vivesienne", +"aigues-vivesiennes", +"aigues-vivesiens", +"aigues-vivien", +"aigues-vivois", +"aigues-vivoise", +"aigues-vivoises", +"aiguise-crayon", +"aiguise-crayons", +"ainu-ken", +"airelle-myrtille", +"aiseau-preslois", +"aka-bea", +"aka-bo", +"aka-cari", +"aka-jeru", +"aka-kede", +"aka-kora", +"akar-bale", +"akhal-teke", +"akua-ba", +"al-Anbar", +"al-Anbâr", +"al-Anbār", +"al-Kachi", +"al-Qaida", +"al-Qaïda", +"albano-letton", +"alcalino-terreuse", +"alcalino-terreuses", +"alcalino-terreux", +"alcool-phénol", +"alcoolo-dépendance", +"alcoolo-dépendances", +"alcools-phénols", +"algo-carburant", +"algo-carburants", +"algéro-marocain", +"algéro-tuniso-lybien", +"algéro-tuniso-marocain", +"allanto-chorion", +"allanto-chorions", +"aller-retour", +"aller-retours", +"allers-retours", +"allez-vous-en", +"allez-y", +"alloxydime-sodium", +"allume-cigare", +"allume-cigares", +"allume-feu", +"allume-feux", +"allume-gaz", +"allumette-bougie", +"allumettes-bougies", +"alpha-amylase", +"alpha-amylases", +"alpha-conversion", +"alpha-conversions", +"alpha-test", +"alpha-tests", +"alpha-tridymite", +"alpha-tridymites", +"alpha-variscite", +"alpha-variscites", +"alsacien-lorrain", +"alto-basso", +"alto-bassos", +"aluminium-épidote", +"aluminium-épidotes", +"alumu-tesu", +"aléseuse-fraiseuse", +"aléseuses-fraiseuses", +"ambre-gris", +"ambystome-tigre", +"ambystomes-tigres", +"ami-ami", +"amiante-ciment", +"amino-acide", +"amino-acides", +"amino-acétique", +"amour-en-cage", +"amour-propre", +"amours-en-cage", +"amours-propres", +"ampli-syntoniseur", +"ampère-heure", +"ampères-heures", +"amuse-bouche", +"amuse-bouches", +"amuse-gueule", +"amuse-gueules", +"analyste-programmeur", +"analystes-programmeurs", +"ananas-bois", +"anarcho-capitalisme", +"anarcho-capitalismes", +"anarcho-fasciste", +"anarcho-fascistes", +"anarcho-punk", +"anarcho-punks", +"anarcho-syndicalisme", +"anarcho-syndicalismes", +"anarcho-syndicaliste", +"anarcho-syndicalistes", +"anatomo-pathologie", +"anatomo-pathologies", +"anatomo-pathologique", +"anatomo-pathologiques", +"andrézien-bouthéonnais", +"andrézienne-bouthéonnaise", +"andréziennes-bouthéonnaises", +"andréziens-bouthéonnais", +"anguille-spaghetti", +"animal-garou", +"animalier-soigneur", +"animaux-garous", +"année-homme", +"année-lumière", +"années-homme", +"années-hommes", +"années-lumière", +"ano-génital", +"ano-génitale", +"ano-génitales", +"ano-génitaux", +"ansbach-triesdorfer", +"ante-bois", +"ante-meridiem", +"ante-meridiems", +"ante-mortem", +"ante-mortems", +"antenne-relais", +"antennes-radar", +"antennes-relais", +"anthropo-gammamétrie", +"anthropo-gammamétries", +"anthropo-toponyme", +"anthropo-toponymes", +"anthropo-zoomorphe", +"anthropo-zoomorphes", +"antiguais-barbudien", +"antiguais-barbudiens", +"antiguais-et-barbudien", +"antiguaise-barbudienne", +"antiguaises-barbudiennes", +"antilope-chevreuil", +"anté-diluvien", +"anté-hypophyse", +"anté-hypophyses", +"anté-prédécesseur", +"anté-prédécesseurs", +"anté-pénultième", +"anté-pénultièmes", +"apico-alvéolaire", +"apico-dental", +"appartement-témoin", +"appartements-témoins", +"appel-contre-appel", +"appels-contre-appels", +"apprenti-sorcellerie", +"apprenti-sorcelleries", +"apprenti-sorcier", +"apprentie-sorcière", +"apprenties-sorcières", +"apprentis-sorciers", +"appui-bras", +"appui-livres", +"appui-main", +"appui-mains", +"appui-pied", +"appui-pieds", +"appui-pot", +"appui-pots", +"appui-tête", +"appui-têtes", +"appuie-main", +"appuie-mains", +"appuie-tête", +"appuie-têtes", +"appuis-main", +"appuis-pot", +"appuis-tête", +"aqua-tinta", +"aqua-toffana", +"aquae-sextien", +"aquae-sextienne", +"aquae-sextiennes", +"aquae-sextiens", +"aquila-alba", +"araignée-crabe", +"araignée-loup", +"araignées-crabes", +"araignées-loups", +"aralo-caspien", +"aralo-caspienne", +"arbre-de-Moïse", +"arbre-à-la-fièvre", +"arbres-de-Moïse", +"arbres-refuges", +"arcado-chypriote", +"arcado-chypriotes", +"arcado-cypriote", +"arcado-cypriotes", +"ardennite-(As)", +"ardennite-(As)s", +"ardi-gasna", +"argent-métal", +"argentite-β", +"argentite-βs", +"argento-analcime", +"argento-analcimes", +"argento-perrylite", +"argento-perrylites", +"argilo-calcaire", +"argilo-calcaires", +"argilo-gréseuse", +"argilo-gréseuses", +"argilo-gréseux", +"argilo-loessique", +"argilo-loessiques", +"argilo-siliceuse", +"argilo-siliceuses", +"argilo-siliceux", +"arginine-méthyla", +"arginine-méthylai", +"arginine-méthylaient", +"arginine-méthylais", +"arginine-méthylait", +"arginine-méthylant", +"arginine-méthylas", +"arginine-méthylasse", +"arginine-méthylassent", +"arginine-méthylasses", +"arginine-méthylassiez", +"arginine-méthylassions", +"arginine-méthyle", +"arginine-méthylent", +"arginine-méthyler", +"arginine-méthylera", +"arginine-méthylerai", +"arginine-méthyleraient", +"arginine-méthylerais", +"arginine-méthylerait", +"arginine-méthyleras", +"arginine-méthylerez", +"arginine-méthyleriez", +"arginine-méthylerions", +"arginine-méthylerons", +"arginine-méthyleront", +"arginine-méthyles", +"arginine-méthylez", +"arginine-méthyliez", +"arginine-méthylions", +"arginine-méthylons", +"arginine-méthylâmes", +"arginine-méthylât", +"arginine-méthylâtes", +"arginine-méthylèrent", +"arginine-méthylé", +"arginine-méthylée", +"arginine-méthylées", +"arginine-méthylés", +"arginine-vasopressine", +"ariaco-dompierrois", +"ariaco-dompierroise", +"ariaco-dompierroises", +"aristo-bourgeoisie", +"aristo-bourgeoisies", +"aristotélico-thomiste", +"aristotélico-thomistes", +"arivey-lingeois", +"arivey-lingeoise", +"arivey-lingeoises", +"armançon-martinois", +"armançon-martinoise", +"armançon-martinoises", +"armbouts-cappellois", +"armbouts-cappelloise", +"armbouts-cappelloises", +"arnaud-guilhémois", +"arnaud-guilhémoise", +"arnaud-guilhémoises", +"arrache-clou", +"arrache-clous", +"arrache-pied", +"arrache-sonde", +"arrow-root", +"arrêt-buffet", +"arrêt-court", +"arrête-boeuf", +"arrête-bœuf", +"arrête-bœufs", +"arrêts-buffet", +"arrêts-courts", +"ars-laquenexois", +"ars-laquenexoise", +"ars-laquenexoises", +"art-thérapie", +"art-thérapies", +"artisan-créateur", +"artisans-créateurs", +"artério-sclérose", +"artério-scléroses", +"assa-foetida", +"assemble-nuages", +"assiette-à-beurre", +"assis-debout", +"assurance-chômage", +"assurance-chômages", +"assurance-emploi", +"assurance-vie", +"assurances-chômage", +"assurances-vie", +"assyro-chaldéen", +"astronome-astrologue", +"astronomes-astrologues", +"astur-léonais", +"ataxie-télangiectasie", +"attache-bossette", +"attache-bossettes", +"attache-doudou", +"attache-doudous", +"attaché-case", +"attaché-cases", +"attachés-cases", +"attentat-suicide", +"attentats-suicides", +"atto-ohm", +"atto-ohms", +"attrape-couillon", +"attrape-couillons", +"attrape-minette", +"attrape-minettes", +"attrape-minon", +"attrape-minons", +"attrape-mouche", +"attrape-mouches", +"attrape-nigaud", +"attrape-nigauds", +"attrape-rêves", +"attrape-tout", +"attrape-vilain", +"au-dedans", +"au-dehors", +"au-delà", +"au-delàs", +"au-dessous", +"au-dessus", +"au-devant", +"au-deçà", +"au-lof", +"au-tour", +"aube-vigne", +"audio-numérique", +"audio-numériques", +"audio-prothésiste", +"audio-prothésistes", +"audio-visuel", +"audio-visuelle", +"audio-visuelles", +"audio-visuels", +"aujourd'hui", +"aulnaie-frênaie", +"aulnaies-frênaies", +"auloi-jumeaux", +"auriculo-ventriculaire", +"auriculo-ventriculaires", +"aurum-musivum", +"aussi-tost", +"aussi-tôt", +"australo-américain", +"austro-asiatique", +"austro-asiatiques", +"austro-hongrois", +"austro-hongroise", +"austro-hongroises", +"austro-occidental", +"austro-occidentale", +"austro-occidentales", +"austro-occidentaux", +"auteur-compositeur", +"auteure-compositrice", +"auteures-compositrices", +"auteurs-compositeurs", +"autos-caravanes", +"autos-mitrailleuses", +"autos-scooters", +"autos-tamponnantes", +"autos-tamponneuses", +"autre-littérature", +"autre-églisois", +"avale-tout", +"avale-tout-cru", +"avale-touts", +"avants-centres", +"avants-postes", +"ave-et-auffois", +"averno-méditerranéen", +"averno-méditerranéenne", +"averno-méditerranéennes", +"averno-méditerranéens", +"aveugle-né", +"aveugle-née", +"aveugles-nés", +"avion-cargo", +"avions-cargos", +"avoir-du-poids", +"axo-missien", +"axo-missienne", +"axo-missiennes", +"axo-missiens", +"ayant-cause", +"ayant-droit", +"ayants-cause", +"ayants-droit", +"aye-aye", +"ayes-ayes", +"ayur-veda", +"azinphos-méthyl", +"azinphos-éthyl", +"aï-aï", +"b-a-ba", +"b.a.-ba", +"baa'thisa", +"baa'thisai", +"baa'thisaient", +"baa'thisais", +"baa'thisait", +"baa'thisant", +"baa'thisas", +"baa'thisasse", +"baa'thisassent", +"baa'thisasses", +"baa'thisassiez", +"baa'thisassions", +"baa'thise", +"baa'thisent", +"baa'thiser", +"baa'thisera", +"baa'thiserai", +"baa'thiseraient", +"baa'thiserais", +"baa'thiserait", +"baa'thiseras", +"baa'thiserez", +"baa'thiseriez", +"baa'thiserions", +"baa'thiserons", +"baa'thiseront", +"baa'thises", +"baa'thisez", +"baa'thisiez", +"baa'thisions", +"baa'thisons", +"baa'thisâmes", +"baa'thisât", +"baa'thisâtes", +"baa'thisèrent", +"baa'thisé", +"baa'thisée", +"baa'thisées", +"baa'thisés", +"babil's", +"babine-witsuwit'en", +"baby-beef", +"baby-beefs", +"baby-boom", +"baby-boomer", +"baby-boomers", +"baby-boomeur", +"baby-boomeurs", +"baby-boomeuse", +"baby-boomeuses", +"baby-foot", +"baby-foots", +"baby-sitter", +"baby-sitters", +"baby-sitting", +"baby-sittings", +"bachat-long", +"bachat-longs", +"bachi-bouzouck", +"bachi-bouzoucks", +"bachi-bouzouk", +"bachi-bouzouks", +"bahá'í", +"bahá'íe", +"bahá'íes", +"bahá'ís", +"baie-mahaultien", +"baie-mahaultienne", +"baie-mahaultiennes", +"baie-mahaultiens", +"baille-blé", +"bain-douche", +"bain-marie", +"bains-douches", +"bains-marie", +"baise-en-ville", +"baise-main", +"bal-musette", +"balai-brosse", +"balais-brosses", +"baleine-pilote", +"baleines-pilotes", +"ball-trap", +"balle-molle", +"balle-queue", +"ballon-panier", +"ballon-sonde", +"ballon-volant", +"ballons-panier", +"ballons-paniers", +"ballons-sondes", +"bals-musette", +"ban-de-lavelinois", +"ban-de-lavelinoise", +"ban-de-lavelinoises", +"ban-saint-martinois", +"ban-saint-martinoise", +"ban-saint-martinoises", +"bana-bana", +"bana-banas", +"banana-split", +"banana-splits", +"bande-annonce", +"bande-son", +"bandes-annonces", +"bank-note", +"bank-notes", +"bar-tabac", +"bar-tabacs", +"barbe-de-Jupiter", +"barbe-de-bouc", +"barbe-de-capucin", +"barbe-de-chèvre", +"barbe-à-papa", +"barbes-de-Jupiter", +"barbes-de-capucin", +"barium-adulaire", +"barium-adulaires", +"barium-anorthite", +"barium-anorthites", +"barium-phlogopite", +"barium-phlogopites", +"barium-sanidine", +"barium-sanidines", +"barré-bandé", +"barrés-bandés", +"bars-tabacs", +"baryton-basse", +"barytons-basses", +"baryum-orthose", +"baryum-orthoses", +"basco-béarnaise", +"basco-navarrais", +"base-ball", +"base-balls", +"base-jump", +"base-jumpeur", +"base-jumpeurs", +"base-jumpeuse", +"base-jumpeuses", +"basi-sphénoïdal", +"basket-ball", +"basket-balls", +"baso-cellulaire", +"baso-cellulaires", +"basque-uruguayen", +"basset-hound", +"bassi-colica", +"bassi-colicas", +"bassin-versant", +"bassins-versants", +"bat-flanc", +"bat-flancs", +"bat-l'eau", +"bat-à-beurre", +"bat-à-bourre", +"bateau-bus", +"bateau-citerne", +"bateau-dragon", +"bateau-feu", +"bateau-lavoir", +"bateau-logement", +"bateau-mouche", +"bateau-mère", +"bateau-phare", +"bateau-usine", +"bateau-vanne", +"bateau-école", +"bateaux-bus", +"bateaux-citernes", +"bateaux-dragons", +"bateaux-feu", +"bateaux-lavoirs", +"bateaux-logements", +"bateaux-mouches", +"bateaux-mères", +"bateaux-phare", +"bateaux-usines", +"bateaux-vanne", +"bateaux-écoles", +"bats-l'eau", +"bats-à-beurre", +"bats-à-bourre", +"battant-l'oeil", +"battant-l'œil", +"battants-l'oeil", +"battants-l'œil", +"batte-lessive", +"batte-mare", +"batte-plate", +"batte-queue", +"battes-plates", +"baussery-montain", +"baussery-montaine", +"baussery-montaines", +"baussery-montains", +"bay-ice", +"bay-ices", +"beach-volley", +"beach-volleys", +"beagle-harrier", +"beau-chasseur", +"beau-dabe", +"beau-fils", +"beau-frais", +"beau-frère", +"beau-livre", +"beau-papa", +"beau-parent", +"beau-partir", +"beau-petit-fils", +"beau-père", +"beau-revoir", +"beau-semblant", +"beaujolais-villages", +"beaux-arts", +"beaux-dabes", +"beaux-enfants", +"beaux-esprits", +"beaux-fils", +"beaux-frères", +"beaux-oncles", +"beaux-parents", +"beaux-petits-fils", +"beaux-pères", +"becque-cornu", +"becques-cornus", +"becs-cornus", +"becs-courbes", +"becs-d'argent", +"becs-d'oie", +"becs-d'âne", +"becs-de-cane", +"becs-de-canon", +"becs-de-cigogne", +"becs-de-cire", +"becs-de-corbeau", +"becs-de-crosse", +"becs-de-cygne", +"becs-de-faucon", +"becs-de-grue", +"becs-de-hache", +"becs-de-héron", +"becs-de-lièvre", +"becs-de-lézard", +"becs-de-perroquet", +"becs-de-pigeon", +"becs-de-vautour", +"becs-durs", +"becs-en-ciseaux", +"becs-en-fourreau", +"becs-ouverts", +"becs-plats", +"becs-pointus", +"becs-ronds", +"becs-tranchants", +"bedlington-terrier", +"behā'ī", +"bekkō-amé", +"bel-enfant", +"bel-esprit", +"bel-oncle", +"bel-outil", +"bel-étage", +"belgo-hollandais", +"belle-d'onze-heures", +"belle-d'un-jour", +"belle-dabe", +"belle-dame", +"belle-de-jour", +"belle-de-nuit", +"belle-doche", +"belle-famille", +"belle-fille", +"belle-fleur", +"belle-maman", +"belle-mère", +"belle-petite-fille", +"belle-pucelle", +"belle-soeur", +"belle-sœur", +"belle-tante", +"belle-à-voir", +"belle-étoile", +"belles-d'un-jour", +"belles-dabes", +"belles-dames", +"belles-de-jour", +"belles-de-nuit", +"belles-doches", +"belles-familles", +"belles-filles", +"belles-fleurs", +"belles-lettres", +"belles-mères", +"belles-pucelles", +"belles-soeurs", +"belles-sœurs", +"belles-tantes", +"belles-étoiles", +"bels-outils", +"ben-ahinois", +"benne-kangourou", +"bensulfuron-méthyle", +"benzoylprop-éthyl", +"berd'huisien", +"berd'huisienne", +"berd'huisiennes", +"berd'huisiens", +"bernard-l'ermite", +"bernard-l'hermite", +"bernico-montois", +"bernico-montoise", +"bernico-montoises", +"bette-marine", +"bettes-marines", +"beun'aise", +"beurre-frais", +"biche-cochon", +"biens-fonds", +"big-endian", +"bil-ka", +"bil-kas", +"bin's", +"bin-bin", +"bin-bins", +"binge-watcha", +"binge-watchai", +"binge-watchaient", +"binge-watchais", +"binge-watchait", +"binge-watchant", +"binge-watchas", +"binge-watchasse", +"binge-watchassent", +"binge-watchasses", +"binge-watchassiez", +"binge-watchassions", +"binge-watche", +"binge-watchent", +"binge-watcher", +"binge-watchera", +"binge-watcherai", +"binge-watcheraient", +"binge-watcherais", +"binge-watcherait", +"binge-watcheras", +"binge-watcherez", +"binge-watcheriez", +"binge-watcherions", +"binge-watcherons", +"binge-watcheront", +"binge-watches", +"binge-watchez", +"binge-watchiez", +"binge-watchions", +"binge-watchons", +"binge-watchâmes", +"binge-watchât", +"binge-watchâtes", +"binge-watchèrent", +"binge-watché", +"binge-watchée", +"binge-watchées", +"binge-watchés", +"bissau-guinéen", +"bistro-brasserie", +"bistro-brasseries", +"bit-el-mal", +"bitter-pit", +"bière-pong", +"bla-bla", +"bla-bla-bla", +"black-bass", +"black-blanc-beur", +"black-bottom", +"black-bottoms", +"black-out", +"black-outa", +"black-outai", +"black-outaient", +"black-outais", +"black-outait", +"black-outant", +"black-outas", +"black-outasse", +"black-outassent", +"black-outasses", +"black-outassiez", +"black-outassions", +"black-oute", +"black-outent", +"black-outer", +"black-outera", +"black-outerai", +"black-outeraient", +"black-outerais", +"black-outerait", +"black-outeras", +"black-outerez", +"black-outeriez", +"black-outerions", +"black-outerons", +"black-outeront", +"black-outes", +"black-outez", +"black-outiez", +"black-outions", +"black-outons", +"black-outs", +"black-outâmes", +"black-outât", +"black-outâtes", +"black-outèrent", +"black-outé", +"black-outée", +"black-outées", +"black-outés", +"black-rot", +"blanche-coiffe", +"blanche-queue", +"blanche-raie", +"blanches-coiffes", +"blancs-becs", +"blancs-bocs", +"blancs-bois", +"blancs-d'Espagne", +"blancs-de-baleine", +"blancs-en-bourre", +"blancs-estocs", +"blancs-mangers", +"blancs-manteaux", +"blancs-raisins", +"blancs-seings", +"blancs-signés", +"blancs-étocs", +"bleu-bite", +"bleu-manteau", +"bleu-merle", +"bleus-manteaux", +"blies-ebersingeois", +"blies-ebersingeoise", +"blies-ebersingeoises", +"blies-ébersingeois", +"blies-ébersingeoise", +"blies-ébersingeoises", +"bling-bling", +"bling-blings", +"blis-et-bornois", +"blis-et-bornoise", +"blis-et-bornoises", +"bloc-cylindres", +"bloc-eau", +"bloc-film", +"bloc-films", +"bloc-moteur", +"bloc-moteurs", +"bloc-note", +"bloc-notes", +"block-système", +"blocs-eau", +"blocs-films", +"blocs-notes", +"blu-ray", +"blue-jean", +"blue-jeans", +"blue-lias", +"boat-people", +"bobby-soxer", +"bobby-soxers", +"body-building", +"boeuf-carotte", +"boissy-maugien", +"boissy-maugienne", +"boissy-maugiennes", +"boissy-maugiens", +"boit-sans-soif", +"bolivo-paraguayen", +"bombardier-torpilleur", +"bombardiers-torpilleurs", +"bon-air", +"bon-bec", +"bon-chrétien", +"bon-creux", +"bon-encontrais", +"bon-encontraise", +"bon-encontraises", +"bon-fieux", +"bon-fils", +"bon-henri", +"bon-mot", +"bon-ouvrier", +"bon-ouvriers", +"bon-papa", +"bon-plein", +"bon-tour", +"bonheur-du-jour", +"bonne-dame", +"bonne-encontre", +"bonne-ente", +"bonne-ententiste", +"bonne-ententistes", +"bonne-femme", +"bonne-grâce", +"bonne-main", +"bonne-maman", +"bonne-vilaine", +"bonne-voglie", +"bonnes-dames", +"bonnes-entes", +"bonnes-femmes", +"bonnes-grâces", +"bonnes-mamans", +"bonnes-vilaines", +"bonnes-voglies", +"bonnet-chinois", +"bonnet-de-prêtre", +"bonnet-rouge", +"bonnet-vert", +"bonnets-chinois", +"bonnets-de-prêtres", +"bonnets-verts", +"bons-chrétiens", +"bons-mots", +"bons-papas", +"boogie-woogie", +"boogie-woogies", +"bord-opposé", +"borde-plats", +"border-terrier", +"bore-out", +"bore-outs", +"borne-couteau", +"borne-fontaine", +"borne-fusible", +"borne-fusibles", +"bornes-couteaux", +"bornes-fontaines", +"bosc-guérardais", +"bosc-guérardaise", +"bosc-guérardaises", +"bosc-renoulthien", +"bosc-renoulthienne", +"bosc-renoulthiennes", +"bosc-renoulthiens", +"bosno-serbe", +"bosno-serbes", +"botte-chaussettes", +"bottom-up", +"bouche-en-flûte", +"bouche-nez", +"bouche-pora", +"bouche-porai", +"bouche-poraient", +"bouche-porais", +"bouche-porait", +"bouche-porant", +"bouche-poras", +"bouche-porasse", +"bouche-porassent", +"bouche-porasses", +"bouche-porassiez", +"bouche-porassions", +"bouche-pore", +"bouche-porent", +"bouche-porer", +"bouche-porera", +"bouche-porerai", +"bouche-poreraient", +"bouche-porerais", +"bouche-porerait", +"bouche-poreras", +"bouche-porerez", +"bouche-poreriez", +"bouche-porerions", +"bouche-porerons", +"bouche-poreront", +"bouche-pores", +"bouche-porez", +"bouche-poriez", +"bouche-porions", +"bouche-porons", +"bouche-porâmes", +"bouche-porât", +"bouche-porâtes", +"bouche-porèrent", +"bouche-poré", +"bouche-porée", +"bouche-porées", +"bouche-porés", +"bouche-trou", +"bouche-trous", +"bouche-à-bouche", +"bouffe-curé", +"bouffe-curés", +"bouffe-galette", +"boui-boui", +"bouig-bouig", +"bouillon-blanc", +"bouis-bouis", +"boulay-morinois", +"boulay-morinoise", +"boulay-morinoises", +"boule-dogue", +"boules-dogues", +"boum-boum", +"bourgeois-bohème", +"bourgeois-bohèmes", +"bourgeoise-bohème", +"bourgeoises-bohèmes", +"bourgue-épine", +"bourgues-épines", +"bourre-chrétien", +"bourre-de-Marseille", +"bourre-goule", +"bourre-goules", +"bourre-noix", +"bourre-pif", +"bourre-pifs", +"bourres-de-Marseille", +"bourse-à-berger", +"bourse-à-pasteur", +"bourses-à-berger", +"bourses-à-pasteur", +"bout-avant", +"bout-d'aile", +"bout-d'argent", +"bout-de-l'an", +"bout-de-manche", +"bout-de-quièvre", +"bout-dehors", +"bout-du-pont-de-l'arnais", +"bout-du-pont-de-l'arnaise", +"bout-du-pont-de-l'arnaises", +"bout-rimé", +"bout-saigneux", +"boute-charge", +"boute-de-lof", +"boute-dehors", +"boute-en-courroie", +"boute-en-train", +"boute-feu", +"boute-hache", +"boute-hors", +"boute-joie", +"boute-lof", +"boute-selle", +"boute-selles", +"boute-tout-cuire", +"boute-à-port", +"boutes-à-port", +"bouton-d'or", +"bouton-poussoir", +"bouton-pression", +"boutons-d'or", +"boutons-pression", +"bouts-avant", +"bouts-d'aile", +"bouts-d'argent", +"bouts-de-l'an", +"bouts-de-manche", +"bouts-de-quièvre", +"bouts-dehors", +"bouts-rimés", +"bouts-saigneux", +"bow-string", +"bow-strings", +"bow-window", +"bow-windows", +"box-calf", +"box-office", +"box-offices", +"boxer-short", +"boxer-shorts", +"boy-scout", +"boy-scouts", +"boîtes-à-musique", +"boîtes-à-musiques", +"bracelet-montre", +"bracelets-montres", +"brachio-céphalique", +"brachio-céphaliques", +"brachio-radial", +"branc-ursine", +"branc-ursines", +"branche-ursine", +"branches-ursines", +"brancs-ursines", +"branle-bas", +"branle-gai", +"branle-long", +"branle-queue", +"branles-bas", +"branles-gais", +"branles-longs", +"branque-ursine", +"bras-d'assien", +"bras-d'assienne", +"bras-d'assiennes", +"bras-d'assiens", +"brash-ice", +"brash-ices", +"brasse-camarade", +"brasse-camarades", +"bray-dunois", +"bray-dunoise", +"bray-dunoises", +"brazza-congolais", +"bredi-breda", +"brelic-breloque", +"brelique-breloque", +"breuil-bernardin", +"breuil-bernardine", +"breuil-bernardines", +"breuil-bernardins", +"breuil-le-secquois", +"breuil-le-secquoise", +"breuil-le-secquoises", +"bric-à-brac", +"brick-goélette", +"brigadier-chef", +"brigadiers-chefs", +"brillat-savarin", +"brillet-pontin", +"brillet-pontine", +"brillet-pontines", +"brillet-pontins", +"brin-d'amour", +"brin-d'estoc", +"brins-d'amour", +"brins-d'estoc", +"bris-d'huis", +"brise-bise", +"brise-bises", +"brise-burnes", +"brise-cou", +"brise-cous", +"brise-fer", +"brise-fers", +"brise-flots", +"brise-glace", +"brise-glaces", +"brise-image", +"brise-images", +"brise-lame", +"brise-lames", +"brise-lunette", +"brise-mariage", +"brise-motte", +"brise-mottes", +"brise-mur", +"brise-murs", +"brise-os", +"brise-pierre", +"brise-pierres", +"brise-raison", +"brise-raisons", +"brise-roche", +"brise-roches", +"brise-scellé", +"brise-scellés", +"brise-soleil", +"brise-tout", +"brise-vent", +"brise-vents", +"bromophos-éthyl", +"broncho-pneumonie", +"broncho-pneumonies", +"broncho-pulmonaire", +"broncho-pulmonaires", +"brou-brou", +"broue-pub", +"broue-pubs", +"brouille-blanche", +"brouille-blanches", +"broute-minou", +"broute-minous", +"brown-nosers", +"brown-out", +"broût-vernetois", +"broût-vernetoise", +"broût-vernetoises", +"bruesme-d'auffe", +"bruesmes-d'auffe", +"brule-gueule", +"brule-gueules", +"brule-maison", +"brule-maisons", +"brule-parfum", +"brule-parfums", +"brun-suisse", +"brut-ingénu", +"brute-bonne", +"bruts-ingénus", +"brèche-dent", +"brèche-dents", +"brécy-brièrois", +"brécy-brièroise", +"brécy-brièroises", +"brûle-amorce", +"brûle-bout", +"brûle-gueule", +"brûle-gueules", +"brûle-maison", +"brûle-maisons", +"brûle-parfum", +"brûle-parfums", +"brûle-pourpoint", +"brûle-queue", +"brûle-tout", +"brûly-de-peschois", +"buccin-marin", +"buccins-marins", +"bucco-dentaire", +"bucco-dentaires", +"bucco-génital", +"bucco-génitale", +"bucco-génitales", +"bucco-génitaux", +"bucco-labial", +"bucco-pharyngé", +"bucco-pharyngée", +"bucco-pharyngées", +"bucco-pharyngés", +"buck-béan", +"buck-béans", +"buen-retiro", +"buenos-airien", +"buis-prévenchais", +"buis-prévenchaise", +"buis-prévenchaises", +"buisson-ardent", +"buissons-ardents", +"bull-dogs", +"bull-mastiff", +"bull-terrier", +"bull-terriers", +"bungee-jumping", +"bungy-jumping", +"bureau-chef", +"burg-reulandais", +"burn-out", +"burn-outa", +"burn-outai", +"burn-outaient", +"burn-outais", +"burn-outait", +"burn-outant", +"burn-outas", +"burn-outasse", +"burn-outassent", +"burn-outasses", +"burn-outassiez", +"burn-outassions", +"burn-oute", +"burn-outent", +"burn-outer", +"burn-outera", +"burn-outerai", +"burn-outeraient", +"burn-outerais", +"burn-outerait", +"burn-outeras", +"burn-outerez", +"burn-outeriez", +"burn-outerions", +"burn-outerons", +"burn-outeront", +"burn-outes", +"burn-outez", +"burn-outiez", +"burn-outions", +"burn-outons", +"burn-outs", +"burn-outâmes", +"burn-outât", +"burn-outâtes", +"burn-outèrent", +"burn-outé", +"burn-outée", +"burn-outées", +"burn-outés", +"buste-reliquaire", +"bustes-reliquaires", +"but-sur-balles", +"butter-oil", +"by-passa", +"by-passai", +"by-passaient", +"by-passais", +"by-passait", +"by-passant", +"by-passas", +"by-passasse", +"by-passassent", +"by-passasses", +"by-passassiez", +"by-passassions", +"by-passe", +"by-passent", +"by-passer", +"by-passera", +"by-passerai", +"by-passeraient", +"by-passerais", +"by-passerait", +"by-passeras", +"by-passerez", +"by-passeriez", +"by-passerions", +"by-passerons", +"by-passeront", +"by-passes", +"by-passez", +"by-passiez", +"by-passions", +"by-passons", +"by-passâmes", +"by-passât", +"by-passâtes", +"by-passèrent", +"by-passé", +"by-passée", +"by-passées", +"by-passés", +"bye-bye", +"bèque-fleur", +"bèque-fleurs", +"bébé-bulle", +"bébé-bus", +"bébé-médicament", +"bébé-nageur", +"bébé-éprouvette", +"bébés-bulles", +"bébés-médicament", +"bébés-nageurs", +"bébés-éprouvette", +"bégler-beg", +"béglier-beg", +"béni-non-non", +"béni-oui-oui", +"bény-bocain", +"bény-bocaine", +"bény-bocaines", +"bény-bocains", +"béta-cyfluthrine", +"béta-gal", +"bêche-de-mer", +"bêches-de-mer", +"bêque-bois", +"bœuf-carotte", +"bœuf-carottes", +"bœuf-garou", +"c'est-à-dire", +"c'que", +"c'qui", +"c'te", +"c-commanda", +"c-commandai", +"c-commandaient", +"c-commandais", +"c-commandait", +"c-commandant", +"c-commandas", +"c-commandasse", +"c-commandassent", +"c-commandasses", +"c-commandassiez", +"c-commandassions", +"c-commande", +"c-commandent", +"c-commander", +"c-commandera", +"c-commanderai", +"c-commanderaient", +"c-commanderais", +"c-commanderait", +"c-commanderas", +"c-commanderez", +"c-commanderiez", +"c-commanderions", +"c-commanderons", +"c-commanderont", +"c-commandes", +"c-commandez", +"c-commandiez", +"c-commandions", +"c-commandons", +"c-commandâmes", +"c-commandât", +"c-commandâtes", +"c-commandèrent", +"c-commandé", +"c-commandée", +"c-commandées", +"c-commandés", +"c-à-d", +"c.-à-d.", +"cabane-roulotte", +"cabanes-roulottes", +"cacasse-à-cul-nu", +"cacasses-à-cul-nu", +"cadrage-débordement", +"caf'conc", +"café-au-lait", +"café-bar", +"café-bistro", +"café-calva", +"café-comptoir", +"café-concert", +"café-crème", +"café-filtre", +"café-théâtre", +"cafés-bars", +"cafés-concerts", +"cafés-crèmes", +"cafés-filtre", +"cafés-théâtres", +"cage-théâtre", +"cages-théâtres", +"cague-braille", +"cague-brailles", +"cahin-caha", +"cail-cédra", +"cail-cédras", +"cail-cédrin", +"cail-cédrins", +"caille-lait", +"caille-laits", +"cailleu-tassart", +"caillot-rosat", +"caillots-rosats", +"caillé-blanc", +"caillés-blancs", +"caisse-outre", +"caisse-palette", +"caisses-outres", +"caisses-palettes", +"cake-walk", +"cake-walks", +"calcite-rhodochrosite", +"calcites-rhodochrosites", +"calcium-autunite", +"calcium-autunites", +"calcium-pyromorphite", +"calcium-pyromorphites", +"calcium-rhodochrosite", +"calcium-rhodochrosites", +"cale-bas", +"cale-dos", +"cale-hauban", +"cale-haubans", +"cale-pied", +"cale-pieds", +"caleçon-combinaison", +"caleçons-combinaisons", +"call-girl", +"call-girls", +"calo-moulinotin", +"calo-moulinotine", +"calo-moulinotines", +"calo-moulinotins", +"came-cruse", +"camion-bélier", +"camion-citerne", +"camion-cuisine", +"camion-cuisines", +"camion-poubelle", +"camions-bennes", +"camions-béliers", +"camions-citernes", +"camions-poubelles", +"camp-volant", +"campanulo-infundibiliforme", +"campanulo-infundibiliformes", +"camping-car", +"camping-cars", +"camping-gaz", +"campo-haltien", +"campo-haltienne", +"campo-haltiennes", +"campo-haltiens", +"campo-laïcien", +"campo-laïcienne", +"campo-laïciennes", +"campo-laïciens", +"camps-volants", +"caméra-lucida", +"caméra-piéton", +"caméra-piétons", +"canadien-français", +"canapé-lit", +"canapés-lits", +"candau-casteidois", +"candau-casteidoise", +"candau-casteidoises", +"cani-joering", +"cani-rando", +"canne-épée", +"cannes-épées", +"cannib's", +"canon-revolver", +"canons-revolvers", +"canoë-kayak", +"canoë-kayaks", +"capelle-filismontin", +"capelle-filismontine", +"capelle-filismontines", +"capelle-filismontins", +"capi-aga", +"capi-agas", +"capigi-bassi", +"capigi-bassis", +"capital-risque", +"capital-risques", +"capital-risqueur", +"capital-risqueurs", +"capitan-pacha", +"capitan-pachas", +"capitaux-risqueurs", +"caporal-chef", +"caporaux-chefs", +"capsule-congé", +"capsules-congés", +"capuchon-de-moine", +"caput-mortuum", +"capélo-hugonais", +"capélo-hugonaise", +"capélo-hugonaises", +"caque-denier", +"car-ferries", +"car-ferry", +"car-ferrys", +"car-jacking", +"carbo-azotine", +"carbonate-apatite", +"carbonate-apatites", +"carbone-14", +"carbones-14", +"carcere-duro", +"cardio-chirurgien", +"cardio-chirurgienne", +"cardio-chirurgiennes", +"cardio-chirurgiens", +"cardio-kickboxing", +"cardio-kickboxings", +"cardio-thoracique", +"cardio-thoraciques", +"cardio-training", +"cardio-vasculaire", +"cardio-vasculaires", +"carfentrazone-éthyle", +"cargo-dortoir", +"cargos-dortoirs", +"caro-percyais", +"caro-percyaise", +"caro-percyaises", +"carré-bossu", +"carrée-bossue", +"carrées-bossues", +"carrés-bossus", +"carte-cadeau", +"carte-fille", +"carte-index", +"carte-lettre", +"carte-maximum", +"carte-mère", +"carte-soleil", +"carte-vue", +"cartes-cadeaux", +"cartes-filles", +"cartes-lettres", +"cartes-maximum", +"cartes-mères", +"cartes-vues", +"carton-index", +"carton-pierre", +"carton-pâte", +"cartons-pâte", +"carême-prenant", +"cas-limite", +"cas-limites", +"cash-back", +"cash-flow", +"cash-flows", +"casque-de-Jupiter", +"casse-aiguille", +"casse-bonbon", +"casse-bonbons", +"casse-bouteille", +"casse-bras", +"casse-burnes", +"casse-bélier", +"casse-béliers", +"casse-claouis", +"casse-coeur", +"casse-coeurs", +"casse-cou", +"casse-couille", +"casse-couilles", +"casse-cous", +"casse-croute", +"casse-croutes", +"casse-croûte", +"casse-croûtes", +"casse-cul", +"casse-culs", +"casse-cœur", +"casse-cœurs", +"casse-dalle", +"casse-dalles", +"casse-fer", +"casse-fil", +"casse-fils", +"casse-graine", +"casse-graines", +"casse-gueule", +"casse-gueules", +"casse-langue", +"casse-langues", +"casse-lunette", +"casse-lunettes", +"casse-mariages", +"casse-motte", +"casse-museau", +"casse-museaux", +"casse-noisette", +"casse-noisettes", +"casse-noix", +"casse-nole", +"casse-noyaux", +"casse-olives", +"casse-patte", +"casse-pattes", +"casse-pied", +"casse-pieds", +"casse-pierre", +"casse-pierres", +"casse-pipe", +"casse-pipes", +"casse-poitrine", +"casse-pot", +"casse-péter", +"casse-tête", +"casse-têtes", +"casse-vessie", +"cassi-ascher", +"cassi-aschers", +"castel-ambillouçois", +"castel-ambillouçoise", +"castel-ambillouçoises", +"castel-chalonnais", +"castel-chalonnaise", +"castel-chalonnaises", +"castel-lévézien", +"castel-lévézienne", +"castel-lévéziennes", +"castel-lévéziens", +"castel-pontin", +"castel-pontine", +"castel-pontines", +"castel-pontins", +"castel-symphorinois", +"castel-symphorinoise", +"castel-symphorinoises", +"castelnau-durbannais", +"castelnau-durbannaise", +"castelnau-durbannaises", +"castet-arrouyais", +"castet-arrouyaise", +"castet-arrouyaises", +"castillano-aragonais", +"cat-boat", +"catalan-valencien-baléare", +"catalase-positive", +"cato-cathartique", +"cato-cathartiques", +"caïque-bazar", +"caïques-bazars", +"cejourd'hui", +"celle-ci", +"celle-là", +"celles-ci", +"celles-là", +"celto-nordique", +"celto-nordiques", +"celui-ci", +"celui-là", +"cent-cinquante-cinq", +"cent-cinquante-cinquièmes", +"cent-garde", +"cent-gardes", +"cent-lances", +"cent-mille", +"cent-suisse", +"cent-suisses", +"centre-bourg", +"centre-droit", +"centre-gauche", +"centre-tir", +"centre-ville", +"centres-bourgs", +"centres-villes", +"cerf-veau", +"cerf-volant", +"cerf-voliste", +"cerfs-veaux", +"cerfs-volants", +"cerfs-volistes", +"certificat-cadeau", +"cesoird'hui", +"cessez-le-feu", +"cession-bail", +"cesta-punta", +"ceux-ci", +"ceux-là", +"ch'kâra", +"ch'kâras", +"ch'ni", +"ch't'aime", +"ch'ti", +"ch'tiisa", +"ch'tiisai", +"ch'tiisaient", +"ch'tiisais", +"ch'tiisait", +"ch'tiisant", +"ch'tiisas", +"ch'tiisasse", +"ch'tiisassent", +"ch'tiisasses", +"ch'tiisassiez", +"ch'tiisassions", +"ch'tiise", +"ch'tiisent", +"ch'tiiser", +"ch'tiisera", +"ch'tiiserai", +"ch'tiiseraient", +"ch'tiiserais", +"ch'tiiserait", +"ch'tiiseras", +"ch'tiiserez", +"ch'tiiseriez", +"ch'tiiserions", +"ch'tiiserons", +"ch'tiiseront", +"ch'tiises", +"ch'tiisez", +"ch'tiisiez", +"ch'tiisions", +"ch'tiisons", +"ch'tiisâmes", +"ch'tiisât", +"ch'tiisâtes", +"ch'tiisèrent", +"ch'tiisé", +"ch'tiisée", +"ch'tiisées", +"ch'tiisés", +"ch'timi", +"ch'tis", +"ch.-l.", +"cha'ban", +"cha-cha", +"cha-cha-cha", +"cha-chas", +"chabada-bada", +"chabazite-Ca", +"chabazite-Cas", +"chabazite-Na", +"chabazite-Nas", +"chambolle-musigny", +"chamboule-tout", +"chamito-sémitique", +"chamito-sémitiques", +"champs-clos", +"changxing'ien", +"chanos-cursonnais", +"chanos-cursonnaise", +"chanos-cursonnaises", +"chantilly-tiffany", +"chape-chuta", +"chape-chutai", +"chape-chutaient", +"chape-chutais", +"chape-chutait", +"chape-chutant", +"chape-chutas", +"chape-chutasse", +"chape-chutassent", +"chape-chutasses", +"chape-chutassiez", +"chape-chutassions", +"chape-chute", +"chape-chutent", +"chape-chuter", +"chape-chutera", +"chape-chuterai", +"chape-chuteraient", +"chape-chuterais", +"chape-chuterait", +"chape-chuteras", +"chape-chuterez", +"chape-chuteriez", +"chape-chuterions", +"chape-chuterons", +"chape-chuteront", +"chape-chutes", +"chape-chutez", +"chape-chutiez", +"chape-chutions", +"chape-chutons", +"chape-chutâmes", +"chape-chutât", +"chape-chutâtes", +"chape-chutèrent", +"chape-chuté", +"chapellois-fortinien", +"chapellois-fortiniens", +"chapelloise-fortinienne", +"chapelloises-fortiniennes", +"chapon-sérésien", +"char-à-bancs", +"charbon-de-pierre", +"charbon-de-terre", +"charbons-de-pierre", +"charbons-de-terre", +"chardon-Marie", +"chardon-Roland", +"chardons-Marie", +"chargeuse-pelleteuse", +"charme-houblon", +"charmes-houblons", +"chars-à-bancs", +"charte-partie", +"chasse-avant", +"chasse-bondieu", +"chasse-bondieux", +"chasse-carrée", +"chasse-carrées", +"chasse-chien", +"chasse-chiens", +"chasse-clou", +"chasse-clous", +"chasse-coquin", +"chasse-cousin", +"chasse-cousins", +"chasse-crapaud", +"chasse-cœur", +"chasse-derrière", +"chasse-derrières", +"chasse-diable", +"chasse-diables", +"chasse-ennui", +"chasse-fièvre", +"chasse-fleurée", +"chasse-fleurées", +"chasse-goupille", +"chasse-goupilles", +"chasse-gueux", +"chasse-marée", +"chasse-marées", +"chasse-morte", +"chasse-mouche", +"chasse-mouches", +"chasse-mulet", +"chasse-mulets", +"chasse-neige", +"chasse-neiges", +"chasse-noix", +"chasse-partie", +"chasse-parties", +"chasse-pierre", +"chasse-pierres", +"chasse-poignée", +"chasse-pointe", +"chasse-pointes", +"chasse-pommeau", +"chasse-punaise", +"chasse-rivet", +"chasse-rivets", +"chasse-rondelle", +"chasse-roue", +"chasse-roues", +"chasse-taupe", +"chasses-parties", +"chasseur-bombardier", +"chasseur-cueilleur", +"chasseurs-bombardiers", +"chasseurs-cueilleurs", +"chassez-déchassez", +"chassez-huit", +"chassé-croisé", +"chassés-croisés", +"chauche-branche", +"chauche-branches", +"chauche-poule", +"chauffe-assiette", +"chauffe-assiettes", +"chauffe-bain", +"chauffe-bains", +"chauffe-biberon", +"chauffe-biberons", +"chauffe-bloc", +"chauffe-blocs", +"chauffe-chemise", +"chauffe-cire", +"chauffe-double", +"chauffe-eau", +"chauffe-eaux", +"chauffe-la-couche", +"chauffe-linge", +"chauffe-linges", +"chauffe-lit", +"chauffe-lits", +"chauffe-moteur", +"chauffe-pied", +"chauffe-pieds", +"chauffe-plat", +"chauffe-plats", +"chauffes-doubles", +"chausse-pied", +"chausse-pieds", +"chausse-trape", +"chausse-trapes", +"chausse-trappe", +"chausse-trappes", +"chauve-souriceau", +"chauve-souricelle", +"chauve-souricière", +"chauve-souricières", +"chauve-souris", +"chauve-souris-garou", +"chauves-souriceaux", +"chauves-souricelles", +"chauves-souris", +"chauves-souris-garous", +"chaux-azote", +"chaux-azotes", +"check-up", +"check-ups", +"cheese-cake", +"cheese-cakes", +"chef-boutonnais", +"chef-boutonnaise", +"chef-boutonnaises", +"chef-d'oeuvre", +"chef-d'œuvre", +"chef-lieu", +"chef-mets", +"chef-mois", +"chefs-d'oeuvre", +"chefs-d'œuvre", +"chefs-lieux", +"cherche-fiche", +"cherche-merde", +"cherche-midi", +"cherche-pointe", +"cheval-fondu", +"cheval-garou", +"cheval-heure", +"cheval-jupon", +"cheval-vapeur", +"chevau-léger", +"chevau-légers", +"chevaux-léger", +"chevaux-légers", +"chevaux-vapeur", +"cheveu-de-Marie-Madeleine", +"cheveux-de-Marie-Madeleine", +"chewing-gum", +"chewing-gums", +"chez-moi", +"chez-soi", +"chez-sois", +"chiche-face", +"chiche-kebab", +"chiche-kébab", +"chiches-faces", +"chiches-kebabs", +"chie-en-lit", +"chie-en-lits", +"chien-assis", +"chien-cerf", +"chien-chaud", +"chien-chauds", +"chien-de-mer", +"chien-garou", +"chien-loup", +"chien-nid", +"chien-rat", +"chienne-louve", +"chiennes-louves", +"chiens-assis", +"chiens-cerf", +"chiens-de-mer", +"chiens-garous", +"chiens-loups", +"chiens-nids", +"chiens-rats", +"chiffre-taxe", +"chiffres-clés", +"chiffres-taxes", +"china-paya", +"chiotte-kès", +"chiottes-kès", +"chirurgien-dentiste", +"chirurgiens-dentistes", +"chloro-IPC", +"chlorpyriphos-méthyl", +"chlorpyriphos-éthyl", +"choano-organismes", +"choche-pierre", +"choche-poule", +"choux-choux", +"choux-fleurs", +"choux-navets", +"choux-palmistes", +"choux-raves", +"chow-chow", +"chow-chows", +"christe-marine", +"christes-marines", +"chrom-brugnatellite", +"chrom-brugnatellites", +"chrome-clinozoïsite", +"chrome-clinozoïsites", +"chrome-fluorite", +"chrome-fluorites", +"chrome-pistazite", +"chrome-pistazites", +"chrome-trémolite", +"chrome-trémolites", +"chrome-zoïsite", +"chrome-zoïsites", +"chrono-localisation", +"chrono-localisations", +"chrétiens-démocrates", +"chuteur-op", +"chuteurs-ops", +"châssis-support", +"châssis-supports", +"châtaigne-d'eau", +"châtaigne-de-mer", +"châtaignes-d'eau", +"châtaignes-de-mer", +"châteauneuf-du-pape", +"châteaux-forts", +"chèque-cadeau", +"chèque-repas", +"chèque-restaurant", +"chèque-vacances", +"chèques-cadeaux", +"chèques-repas", +"chèques-restaurants", +"chèques-vacances", +"chèvre-choutiste", +"chèvre-choutistes", +"chèvre-feuille", +"chèvre-pied", +"chèvre-pieds", +"chèvres-feuilles", +"chéry-chartreuvois", +"chéry-chartreuvoise", +"chéry-chartreuvoises", +"chêne-gomme", +"chêne-liège", +"chêne-marin", +"chêne-pommier", +"chênes-gommes", +"chênes-lièges", +"chênes-marins", +"ci-après", +"ci-attaché", +"ci-contre", +"ci-delez", +"ci-dessous", +"ci-dessus", +"ci-devant", +"ci-gisent", +"ci-git", +"ci-gît", +"ci-haut", +"ci-hauts", +"ci-incluse", +"ci-incluses", +"ci-joint", +"ci-jointe", +"ci-jointes", +"ci-joints", +"ci-plus-bas", +"ci-plus-haut", +"cia-cia", +"cinq-cents", +"cinq-dix-quinze", +"cinq-huitième", +"cinq-marsien", +"cinq-marsienne", +"cinq-marsiennes", +"cinq-marsiens", +"cinq-mâts", +"cinq-quatre-un", +"cinq-six", +"cinquante-cinq", +"cinquante-cinquante", +"cinquante-deux", +"cinquante-et-un", +"cinquante-et-une", +"cinquante-et-unième", +"cinquante-et-unièmes", +"cinquante-huit", +"cinquante-neuf", +"cinquante-quatre", +"cinquante-sept", +"cinquante-six", +"cinquante-trois", +"ciné-club", +"ciné-clubs", +"ciné-parc", +"cinéma-dinatoire", +"cinéma-dinatoires", +"circolo-mezzo", +"circonscriptions-clés", +"circum-aural", +"circum-continental", +"cire-pompe", +"cire-pompes", +"cirque-ménagerie", +"cirque-théâtre", +"cirques-ménageries", +"cirques-théâtres", +"cis-gangétique", +"cis-gangétiques", +"cis-verbénol", +"citizen-band", +"citron-pays", +"citrons-pays", +"cité-dortoir", +"cité-État", +"cités-dortoirs", +"cités-États", +"clac-clac", +"clac-clacs", +"claque-merde", +"claque-oreille", +"claque-oreilles", +"claque-patin", +"claque-patins", +"clavi-cylindre", +"clavi-harpe", +"clavi-lyre", +"clic-clac", +"client-cible", +"client-cibles", +"client-serveur", +"cligne-musette", +"climato-sceptique", +"climato-sceptiques", +"clin-foc", +"clin-focs", +"cloche-pied", +"cloche-pieds", +"cloche-plaque", +"clodinafop-propargyl", +"clopin-clopant", +"cloquintocet-mexyl", +"clos-fontainois", +"clos-fontainoise", +"clos-fontainoises", +"clos-masure", +"clos-masures", +"clos-vougeot", +"clos-vougeots", +"club-house", +"clubs-houses", +"clématite-viorne", +"clématites-viornes", +"clérico-nationaliste", +"clérico-nationalistes", +"coat-méalien", +"coat-méalienne", +"coat-méaliennes", +"coat-méaliens", +"cobalt-gris", +"cobalt-mica", +"cobalt-ochre", +"cobalto-sphaérosidérite", +"cobalto-sphaérosidérites", +"cobalto-épsomite", +"cobalto-épsomites", +"cobalts-gris", +"cobalts-micas", +"cobalts-ochres", +"cochon-garou", +"cochons-garous", +"coco-de-mer", +"coco-fesses", +"cocotte-minute", +"codes-barres", +"codes-clés", +"coeur-de-pigeon", +"coeurs-de-pigeon", +"coeurs-de-pigeons", +"coffre-fort", +"coffres-forts", +"coin-coin", +"coin-coins", +"col-nu", +"col-vert", +"col-verts", +"colin-maillard", +"colin-tampon", +"colis-route", +"colis-routes", +"collant-pipette", +"collant-pipettes", +"collet-monté", +"colloid-calcite", +"colloid-calcites", +"collé-serré", +"collés-serrés", +"cols-nus", +"cols-verts", +"colville-okanagan", +"com'com", +"combi-short", +"combi-shorts", +"comble-lacune", +"comble-lacunes", +"come-back", +"commis-voyageur", +"commis-voyageurs", +"commissaire-priseur", +"commissaires-priseurs", +"compositeur-typographe", +"compositeur-typographes", +"comptes-rendus", +"compère-loriot", +"compères-loriot", +"comédie-ballet", +"comédies-ballets", +"concavo-concave", +"concavo-convexe", +"conforte-main", +"conférences-débats", +"congo-kinois", +"congolo-kinois", +"congolo-kinoise", +"congolo-kinoises", +"conseil-général", +"contra-latéral", +"contrat-cadre", +"contrats-cadres", +"contrôle-commande", +"convexo-concave", +"copia-colla", +"copiable-collable", +"copiables-collables", +"copiage-collage", +"copiages-collages", +"copiai-collai", +"copiaient-collaient", +"copiais-collais", +"copiait-collait", +"copiant-collant", +"copias-collas", +"copiasse-collasse", +"copiassent-collassent", +"copiasses-collasses", +"copiassiez-collassiez", +"copiassions-collassions", +"copie-colle", +"copie-lettres", +"copient-collent", +"copier-coller", +"copier-collers", +"copiera-collera", +"copierai-collerai", +"copieraient-colleraient", +"copierais-collerais", +"copierait-collerait", +"copieras-colleras", +"copierez-collerez", +"copieriez-colleriez", +"copierions-collerions", +"copierons-collerons", +"copieront-colleront", +"copies-colles", +"copiez-collez", +"copiez-colliez", +"copions-collions", +"copions-collons", +"copiâmes-collâmes", +"copiât-collât", +"copiâtes-collâtes", +"copièrent-collèrent", +"copié-collé", +"copié-collés", +"copiée-collée", +"copiées-collées", +"copiés-collés", +"coq-de-roche", +"coq-héron", +"coq-souris", +"coq-à-l'âne", +"coqs-de-roche", +"coquel'œil", +"coquel'œils", +"coral-rag", +"corbeau-pêcheur", +"corbeaux-pêcheurs", +"corbeil-essonnois", +"corbeil-essonnoise", +"corbeil-essonnoises", +"cordons-bleus", +"corn-flake", +"corn-flakes", +"corned-beef", +"corned-beefs", +"corps-mort", +"corps-morts", +"cortico-cortical", +"cortico-corticale", +"cortico-corticales", +"cortico-corticaux", +"cortil-noirmontois", +"costa-ricien", +"costa-ricienne", +"costa-riciennes", +"costa-riciens", +"costard-cravate", +"costards-cravates", +"costo-claviculaire", +"costo-sternal", +"costo-thoracique", +"costo-vertébral", +"costo-vertébrale", +"costo-vertébrales", +"costo-vertébraux", +"cosy-corner", +"cosy-corners", +"coton-poudre", +"coton-poudres", +"coton-tige", +"cotons-poudres", +"cotons-tiges", +"cotte-hardie", +"cottes-hardies", +"cou-de-jatte", +"cou-de-pied", +"cou-jaune", +"cou-nu", +"couble-soiffière", +"couche-culotte", +"couche-point", +"couche-points", +"couche-tard", +"couche-tôt", +"couches-culottes", +"couci-couci", +"couci-couça", +"coude-pied", +"coude-à-coude", +"coule-sang", +"couper-coller", +"coupon-réponse", +"coupons-réponses", +"coups-de-poing", +"coupé-cabriolet", +"coupé-collé", +"coupé-décalé", +"coupé-lit", +"coupés-cabriolets", +"coupés-collés", +"coupés-décalés", +"coupés-lits", +"cour-masure", +"courant-jet", +"courants-jets", +"coure-vite", +"cours-de-pilois", +"cours-de-piloise", +"cours-de-piloises", +"course-poursuite", +"courses-poursuites", +"courte-botte", +"courte-graisse", +"courte-lettre", +"courte-pointe", +"courte-pointier", +"courte-queue", +"courte-épine", +"courte-épines", +"courte-épée", +"courtes-bottes", +"courtes-lettres", +"courtes-pattes", +"courtes-pointes", +"courtes-queues", +"courtes-épées", +"courts-bandages", +"courts-boutons", +"courts-circuits", +"courts-cureaux", +"courts-côtés", +"courts-jus", +"courts-métrages", +"courts-tours", +"cous-cous", +"cous-de-jatte", +"cous-de-pied", +"cous-jaunes", +"cout'donc", +"couteau-de-chasse", +"couteau-scie", +"couteaux-de-chasse", +"couteaux-scie", +"couvre-casque", +"couvre-casques", +"couvre-chaussure", +"couvre-chaussures", +"couvre-chef", +"couvre-chefs", +"couvre-clef", +"couvre-clefs", +"couvre-face", +"couvre-faces", +"couvre-feu", +"couvre-feux", +"couvre-giberne", +"couvre-gibernes", +"couvre-joint", +"couvre-joints", +"couvre-lit", +"couvre-lits", +"couvre-livre", +"couvre-livres", +"couvre-lumière", +"couvre-lumières", +"couvre-manche", +"couvre-manches", +"couvre-nuque", +"couvre-nuques", +"couvre-objet", +"couvre-objets", +"couvre-orteil", +"couvre-orteils", +"couvre-pied", +"couvre-pieds", +"couvre-plat", +"couvre-plats", +"couvre-shako", +"couvre-shakos", +"couvre-sol", +"couvre-sols", +"couvreur-zingueur", +"cover-girl", +"cover-girls", +"cow-boy", +"cow-boys", +"coxa-retrorsa", +"coxo-fémoral", +"crabe-araignée", +"crabes-araignées", +"crac-crac", +"crachouillot-thérapeute", +"craignant-Dieu", +"cran-gevrien", +"cran-gevrienne", +"cran-gevriennes", +"cran-gevriens", +"cranio-facial", +"cranves-salien", +"cranves-saliens", +"cranves-saliène", +"cranves-saliènes", +"crapaud-buffle", +"crapauds-buffles", +"crapet-soleil", +"crayon-feutre", +"crayon-souris", +"crayons-feutre", +"crayons-feutres", +"crest-volantain", +"crest-volantaine", +"crest-volantaines", +"crest-volantains", +"crevette-mante", +"crevettes-mantes", +"cri-cri", +"cri-cris", +"cric-crac", +"crico-trachéal", +"crico-trachéale", +"crico-trachéales", +"crico-trachéaux", +"cristallo-électrique", +"cristallo-électriques", +"criste-marine", +"croad-langshan", +"croc-en-jambe", +"crocs-en-jambe", +"croiseur-école", +"croiseurs-écoles", +"croix-caluois", +"croix-caluoise", +"croix-caluoises", +"croix-de-Malte", +"croix-de-feu", +"croix-pile", +"croix-roussien", +"croix-roussienne", +"croix-roussiennes", +"croix-roussiens", +"cromlec'h", +"cromlec'hs", +"croque-abeilles", +"croque-au-sel", +"croque-en-bouche", +"croque-lardon", +"croque-lardons", +"croque-madame", +"croque-madames", +"croque-mademoiselle", +"croque-mademoiselles", +"croque-messieurs", +"croque-mitaine", +"croque-mitaines", +"croque-monsieur", +"croque-monsieurs", +"croque-mort", +"croque-morts", +"croque-moutons", +"croque-noisette", +"croque-noisettes", +"croque-noix", +"croque-note", +"crossing-over", +"crotte-du-Diable", +"crotte-du-diable", +"crottes-du-Diable", +"crottes-du-diable", +"crown-glass", +"cruci-capétien", +"cruci-capétienne", +"cruci-capétiennes", +"cruci-capétiens", +"cruci-falgardien", +"cruci-falgardienne", +"cruci-falgardiennes", +"cruci-falgardiens", +"crud-ammoniac", +"crypto-communiste", +"crypto-luthérien", +"crypto-luthérienne", +"crypto-luthériennes", +"crypto-luthériens", +"crypto-monnaie", +"crypto-monnaies", +"crève-chassis", +"crève-chien", +"crève-chiens", +"crève-coeur", +"crève-coeurs", +"crève-cœur", +"crève-cœurs", +"crève-la-dalle", +"crève-la-faim", +"crève-vessie", +"crève-vessies", +"créateur-typographe", +"crédit-bail", +"crédit-temps", +"crédits-bail", +"crédits-bails", +"crédits-baux", +"crédits-temps", +"crête-de-coq", +"crête-marine", +"crêtes-de-coq", +"crêtes-marines", +"cubito-carpien", +"cubito-carpienne", +"cubito-carpiennes", +"cubito-carpiens", +"cubo-prismatique", +"cubo-prismatiques", +"cucu-la-praline", +"cucul-la-praline", +"cueille-essaim", +"cueille-fruits", +"cueilleur-égreneur", +"cueilleurs-égreneurs", +"cueilleuse-égreneuse", +"cueilleuse-épanouilleuse", +"cueilleuses-égreneuses", +"cueilleuses-épanouilleuses", +"cui-cui", +"cuir-laine", +"cuiry-houssien", +"cuiry-houssienne", +"cuiry-houssiennes", +"cuiry-houssiens", +"cuisse-de-nymphe", +"cuisse-madame", +"cuisse-madames", +"cuit-poires", +"cuit-pommes", +"cuit-vapeur", +"cuit-vapeurs", +"cul-bas", +"cul-blanc", +"cul-brun", +"cul-bénit", +"cul-cul", +"cul-culs", +"cul-de-basse-fosse", +"cul-de-bouteille", +"cul-de-chien", +"cul-de-four", +"cul-de-jatte", +"cul-de-lampe", +"cul-de-plomb", +"cul-de-porc", +"cul-de-poule", +"cul-de-sac", +"cul-des-sartois", +"cul-doré", +"cul-levé", +"cul-rouge", +"cul-rousselet", +"cul-terreux", +"culcul-la-praline", +"culit-api", +"culs-blancs", +"culs-bénits", +"culs-de-basse-fosse", +"culs-de-bouteille", +"culs-de-chien", +"culs-de-four", +"culs-de-jatte", +"culs-de-lampe", +"culs-de-plomb", +"culs-de-poule", +"culs-de-sac", +"culs-levés", +"culs-rouges", +"culs-terreux", +"cultivateur-tasseur", +"cultivateurs-tasseurs", +"culturo-scientifique", +"culturo-scientifiques", +"cumulo-nimbus", +"cunéo-scaphoïdien", +"cupro-allophane", +"cupro-allophanes", +"cupro-aluminium", +"cupro-aluminiums", +"cupro-ammoniacal", +"cupro-elbaïte", +"cupro-elbaïtes", +"cupro-fraipontite", +"cupro-fraipontites", +"cupro-nickel", +"cupro-nickels", +"cure-dent", +"cure-dents", +"cure-feu", +"cure-feux", +"cure-langue", +"cure-langues", +"cure-môle", +"cure-ongle", +"cure-ongles", +"cure-oreille", +"cure-oreilles", +"cure-pied", +"cure-pieds", +"cure-pipe", +"cure-pipes", +"curti-marignacais", +"curti-marignacaise", +"curti-marignacaises", +"custodi-nos", +"cycle-car", +"cycle-cars", +"cyclo-bus", +"cyclo-cross", +"cyclo-draisine", +"cyclo-draisines", +"cyclo-nomade", +"cyclo-nomades", +"cyclo-octyl-diméthylurée", +"cyclo-pousse", +"cyclo-pousses", +"cyhalofop-butyl", +"cylindro-conique", +"cyth's", +"cyto-architectonie", +"cyto-architectonies", +"cyto-architectonique", +"cyto-architectoniques", +"câblo-opérateur", +"câblo-opérateurs", +"cèleri-rave", +"cèleri-raves", +"cédez-le-passage", +"céleri-rave", +"céleris-raves", +"céléri-rave", +"céphalo-pharyngien", +"céphalo-pharyngienne", +"céphalo-pharyngiennes", +"céphalo-pharyngiens", +"céphalo-rachidien", +"cérébro-lésion", +"cérébro-lésions", +"cérébro-rachidien", +"cérébro-rachidienne", +"cérébro-rachidiennes", +"cérébro-rachidiens", +"cérébro-spinal", +"cérébro-spinale", +"cérébro-spinales", +"cérébro-spinaux", +"césaro-papisme", +"césaro-papismes", +"césaro-papiste", +"césaro-papistes", +"césium-analcime", +"césium-analcimes", +"côtes-de-toul", +"côtes-du-Rhône", +"côtes-du-rhône", +"côtes-du-rhônes", +"cœur-de-Jeannette", +"cœur-de-pigeon", +"cœurs-de-pigeons", +"d-amphétamine", +"dalai-lama", +"dalai-lamas", +"dalaï-lama", +"dalaï-lamas", +"dame-aubert", +"dame-d'onze-heures", +"dame-jeanne", +"dame-pipi", +"dame-ronde", +"dames-d'onze-heures", +"dames-jeannes", +"dames-pipi", +"dames-rondes", +"danse-poteau", +"dar-et-dar", +"dare-dare", +"datte-de-mer", +"de-ci", +"de-là", +"dead-line", +"dead-lines", +"dena'ina", +"dena'inas", +"dent-de-cheval", +"dent-de-chien", +"dent-de-lion", +"dent-de-loup", +"dent-de-rat", +"dento-facial", +"dents-de-cheval", +"dents-de-chien", +"dents-de-lion", +"dermato-allergologue", +"dermato-allergologues", +"dernier-né", +"dernier-nés", +"derniers-nés", +"dernière-née", +"des-agreable", +"des-agreables", +"dessinateur-typographe", +"dessous-de-bouteille", +"dessous-de-bras", +"dessous-de-plat", +"dessous-de-table", +"dessous-de-tables", +"dessus-de-lit", +"dessus-de-plat", +"dessus-de-porte", +"dessus-de-tête", +"deux-cent-vingt-et-un", +"deux-cents", +"deux-chaisois", +"deux-chaisoise", +"deux-chaisoises", +"deux-chevaux", +"deux-dents", +"deux-mille", +"deux-mâts", +"deux-peccable", +"deux-peccables", +"deux-pièces", +"deux-points", +"deux-ponts", +"deux-quatre", +"deux-roues", +"deux-temps", +"devrai-gondragnier", +"devrai-gondragniers", +"devrai-gondragnière", +"devrai-gondragnières", +"dextro-volubile", +"di-1-p-menthène", +"diam's", +"diastéréo-isomère", +"diastéréo-isomères", +"dichloro-diphényl-dichloroéthane", +"dichlorprop-p", +"diclofop-méthyl", +"diesel-électrique", +"diesels-électriques", +"digue-digue", +"dihydro-oxycodéinone", +"dik-dik", +"dik-diks", +"dikégulac-sodium", +"diméthyl-dixanthogène", +"diméthénamide-P", +"dining-room", +"dining-rooms", +"diola-kasa", +"diony-sapinois", +"diony-sapinoise", +"diony-sapinoises", +"diptéro-sodomie", +"diptéro-sodomies", +"disc-jockey", +"disc-jockeys", +"distance-temps", +"divergi-nervé", +"dix-cors", +"dix-en-dix", +"dix-heura", +"dix-heurai", +"dix-heuraient", +"dix-heurais", +"dix-heurait", +"dix-heurant", +"dix-heuras", +"dix-heurasse", +"dix-heurassent", +"dix-heurasses", +"dix-heurassiez", +"dix-heurassions", +"dix-heure", +"dix-heurent", +"dix-heurer", +"dix-heurera", +"dix-heurerai", +"dix-heureraient", +"dix-heurerais", +"dix-heurerait", +"dix-heureras", +"dix-heurerez", +"dix-heureriez", +"dix-heurerions", +"dix-heurerons", +"dix-heureront", +"dix-heures", +"dix-heurez", +"dix-heuriez", +"dix-heurions", +"dix-heurons", +"dix-heurâmes", +"dix-heurât", +"dix-heurâtes", +"dix-heurèrent", +"dix-heuré", +"dix-huit", +"dix-huitième", +"dix-huitièmement", +"dix-huitièmes", +"dix-huitièmiste", +"dix-huitièmistes", +"dix-huitiémisme", +"dix-huitiémismes", +"dix-huitiémiste", +"dix-huitiémistes", +"dix-mille", +"dix-millionième", +"dix-millionièmes", +"dix-millième", +"dix-millièmes", +"dix-neuf", +"dix-neuvième", +"dix-neuvièmement", +"dix-neuvièmes", +"dix-neuvièmiste", +"dix-neuvièmistes", +"dix-neuviémisme", +"dix-neuviémismes", +"dix-neuviémiste", +"dix-neuviémistes", +"dix-roues", +"dix-sept", +"dix-septième", +"dix-septièmement", +"dix-septièmes", +"dix-septièmiste", +"dix-septièmistes", +"dix-septiémisme", +"dix-septiémismes", +"dix-septiémiste", +"dix-septiémistes", +"diésel-électrique", +"diésels-électriques", +"diéthyl-diphényl-dichloroéthane", +"djoumada-l-oula", +"djoumada-t-tania", +"doati-casteidois", +"doati-casteidoise", +"doati-casteidoises", +"docu-fiction", +"docu-fictions", +"documentaire-choc", +"documentaires-chocs", +"dodémorphe-acétate", +"dog-cart", +"dog-carts", +"doigt-de-gant", +"doigts-de-gant", +"dom-tomien", +"dom-tomienne", +"dom-tomiennes", +"dom-tomiens", +"dommage-intérêt", +"dommages-intérêts", +"dompte-venin", +"dompte-venins", +"don-juanisme", +"don-juanismes", +"don-quichottisme", +"don-quichottismes", +"donation-partage", +"donations-partages", +"donnant-donnant", +"donne-jour", +"doom-death", +"dorso-vélaire", +"dorso-vélaires", +"dos-d'âne", +"dou-l-hidjja", +"dou-l-qa'da", +"doubet-talibautier", +"doubet-talibautiers", +"doubet-talibautière", +"doubet-talibautières", +"doubles-aubiers", +"doubles-bouches", +"doubles-bulbes", +"doubles-bécassines", +"doubles-canons", +"doubles-chaînes", +"doubles-clics", +"doubles-croches", +"doubles-feuilles", +"doubles-fonds", +"doubles-mains", +"doubles-sens", +"douce-amère", +"douces-amères", +"doux-agnel", +"doux-amer", +"doux-amers", +"doux-ballon", +"doux-vert", +"doux-à-l'agneau", +"down-loada", +"down-loadai", +"down-loadaient", +"down-loadais", +"down-loadait", +"down-loadant", +"down-loadas", +"down-loadasse", +"down-loadassent", +"down-loadasses", +"down-loadassiez", +"down-loadassions", +"down-loade", +"down-loadent", +"down-loader", +"down-loadera", +"down-loaderai", +"down-loaderaient", +"down-loaderais", +"down-loaderait", +"down-loaderas", +"down-loaderez", +"down-loaderiez", +"down-loaderions", +"down-loaderons", +"down-loaderont", +"down-loades", +"down-loadez", +"down-loadiez", +"down-loadions", +"down-loadons", +"down-loadâmes", +"down-loadât", +"down-loadâtes", +"down-loadèrent", +"down-loadé", +"down-loadée", +"down-loadées", +"down-loadés", +"dragonnet-lyre", +"drainage-taupe", +"draineuse-trancheuse", +"draineuses-trancheuses", +"drap-housse", +"drap-housses", +"drelin-drelin", +"drift-ice", +"drift-ices", +"dring-dring", +"drive-in", +"drive-ins", +"drive-way", +"drive-ways", +"droit-fil", +"droit-fils", +"drop-goal", +"drop-goals", +"drug-store", +"drug-stores", +"dry-tooleur", +"dry-tooleurs", +"dry-tooling", +"dual-core", +"dual-cores", +"duc-d'Albe", +"duc-d'albe", +"duché-pairie", +"duchés-pairies", +"ducs-d'Albe", +"ducs-d'albe", +"duffel-coat", +"duffel-coats", +"duffle-coat", +"duffle-coats", +"dum-dum", +"duo-tang", +"duo-tangs", +"duplicato-dentelé", +"dur-bec", +"dure-mère", +"dure-peau", +"dures-mères", +"dures-peaux", +"durs-becs", +"duty-free", +"dynamo-électrique", +"dynamo-électriques", +"dès-méshui", +"débat-spectacle", +"débauche-embauche", +"déca-ampère", +"déca-ampères", +"découd-vite", +"découpe-neige", +"découpes-neige", +"décrochez-moi-ça", +"déjà-vu", +"démocrate-chrétien", +"démocrate-chrétienne", +"démocrates-chrétiennes", +"démocrates-chrétiens", +"démonte-pneu", +"démonte-pneus", +"déméton-méthyl", +"dépose-minute", +"député-maire", +"députés-maires", +"dépôt-vente", +"dépôts-ventes", +"déséthyl-terbuméton", +"dîner-spectacle", +"dîners-spectacles", +"e-administration", +"e-administrations", +"e-book", +"e-business", +"e-carte", +"e-cartes", +"e-cig", +"e-cigarette", +"e-cigarettes", +"e-cigs", +"e-cinéma", +"e-cinémas", +"e-client", +"e-clope", +"e-clopes", +"e-commerce", +"e-commerçant", +"e-commerçants", +"e-couponing", +"e-criminalité", +"e-criminalités", +"e-délinquance", +"e-délinquances", +"e-la", +"e-la-fa", +"e-la-mi", +"e-mail", +"e-maila", +"e-mailai", +"e-mailaient", +"e-mailais", +"e-mailait", +"e-mailant", +"e-mailas", +"e-mailasse", +"e-mailassent", +"e-mailasses", +"e-mailassiez", +"e-mailassions", +"e-maile", +"e-mailent", +"e-mailer", +"e-mailera", +"e-mailerai", +"e-maileraient", +"e-mailerais", +"e-mailerait", +"e-maileras", +"e-mailerez", +"e-maileriez", +"e-mailerions", +"e-mailerons", +"e-maileront", +"e-mailes", +"e-maileur", +"e-maileurs", +"e-maileuse", +"e-maileuses", +"e-mailez", +"e-mailiez", +"e-mailing", +"e-mailings", +"e-mailions", +"e-mailons", +"e-mailâmes", +"e-mailât", +"e-mailâtes", +"e-mailèrent", +"e-mailé", +"e-mailée", +"e-mailées", +"e-mailés", +"e-marketeur", +"e-marketeurs", +"e-marketeuse", +"e-marketeuses", +"e-marketing", +"e-marketings", +"e-merchandiser", +"e-procurement", +"e-procurements", +"e-reader", +"e-readers", +"e-réputation", +"e-réputations", +"e-réservation", +"e-réservations", +"e-santé", +"e-sport", +"e-sportif", +"e-sportifs", +"e-sports", +"e-ticket", +"e-tickets", +"e-tourisme", +"eau-bénitier", +"eau-bénitiers", +"eau-de-vie", +"eau-forte", +"eaux-bonnais", +"eaux-bonnaise", +"eaux-bonnaises", +"eaux-de-vie", +"eaux-fortes", +"eaux-vannes", +"edit-a-thon", +"edit-a-thons", +"effet-bulle", +"effets-bulles", +"ego-document", +"ego-documents", +"el-âsker", +"elle-même", +"elles-mêmes", +"ello-rhénan", +"ello-rhénane", +"ello-rhénanes", +"ello-rhénans", +"emballage-bulle", +"emballage-coque", +"emballages-bulles", +"emballages-coques", +"emo-sexualité", +"emo-sexualités", +"emporte-pièce", +"emporte-pièces", +"en-avant", +"en-avants", +"en-but", +"en-buts", +"en-cas", +"en-cours", +"en-dessous", +"en-dessus", +"en-deçà", +"en-garant", +"en-tout-cas", +"en-tête", +"en-têtes", +"enfant-bulle", +"enfant-roi", +"enfant-soldat", +"enfants-bulles", +"enfants-robots", +"enfants-rois", +"enfants-soldats", +"enfile-aiguille", +"enfile-aiguilles", +"enfle-boeuf", +"enfle-boeufs", +"enfle-bœuf", +"enfle-bœufs", +"enquêtes-minute", +"enseignant-chercheur", +"enseignante-chercheuse", +"enseignantes-chercheuses", +"enseignants-chercheurs", +"entr'abat", +"entr'abattaient", +"entr'abattait", +"entr'abattant", +"entr'abatte", +"entr'abattent", +"entr'abattez", +"entr'abattiez", +"entr'abattions", +"entr'abattirent", +"entr'abattissent", +"entr'abattissions", +"entr'abattit", +"entr'abattons", +"entr'abattra", +"entr'abattraient", +"entr'abattrait", +"entr'abattre", +"entr'abattrez", +"entr'abattriez", +"entr'abattrions", +"entr'abattrons", +"entr'abattront", +"entr'abattu", +"entr'abattue", +"entr'abattues", +"entr'abattus", +"entr'abattîmes", +"entr'abattît", +"entr'abattîtes", +"entr'aborda", +"entr'abordaient", +"entr'abordait", +"entr'abordant", +"entr'abordassent", +"entr'abordassiez", +"entr'abordassions", +"entr'aborde", +"entr'abordent", +"entr'aborder", +"entr'abordera", +"entr'aborderaient", +"entr'aborderait", +"entr'aborderez", +"entr'aborderiez", +"entr'aborderions", +"entr'aborderons", +"entr'aborderont", +"entr'abordez", +"entr'abordiez", +"entr'abordions", +"entr'abordons", +"entr'abordâmes", +"entr'abordât", +"entr'abordâtes", +"entr'abordèrent", +"entr'abordé", +"entr'abordées", +"entr'abordés", +"entr'accola", +"entr'accolaient", +"entr'accolait", +"entr'accolant", +"entr'accolassent", +"entr'accolassiez", +"entr'accolassions", +"entr'accole", +"entr'accolent", +"entr'accoler", +"entr'accolera", +"entr'accoleraient", +"entr'accolerait", +"entr'accolerez", +"entr'accoleriez", +"entr'accolerions", +"entr'accolerons", +"entr'accoleront", +"entr'accolez", +"entr'accoliez", +"entr'accolions", +"entr'accolons", +"entr'accolâmes", +"entr'accolât", +"entr'accolâtes", +"entr'accolèrent", +"entr'accolé", +"entr'accolées", +"entr'accolés", +"entr'accorda", +"entr'accordaient", +"entr'accordait", +"entr'accordant", +"entr'accordassent", +"entr'accordassiez", +"entr'accordassions", +"entr'accorde", +"entr'accordent", +"entr'accorder", +"entr'accordera", +"entr'accorderaient", +"entr'accorderait", +"entr'accorderez", +"entr'accorderiez", +"entr'accorderions", +"entr'accorderons", +"entr'accorderont", +"entr'accordez", +"entr'accordiez", +"entr'accordions", +"entr'accordons", +"entr'accordâmes", +"entr'accordât", +"entr'accordâtes", +"entr'accordèrent", +"entr'accordé", +"entr'accordées", +"entr'accordés", +"entr'accrocha", +"entr'accrochaient", +"entr'accrochait", +"entr'accrochant", +"entr'accrochassent", +"entr'accrochassiez", +"entr'accrochassions", +"entr'accroche", +"entr'accrochent", +"entr'accrocher", +"entr'accrochera", +"entr'accrocheraient", +"entr'accrocherait", +"entr'accrocherez", +"entr'accrocheriez", +"entr'accrocherions", +"entr'accrocherons", +"entr'accrocheront", +"entr'accrochez", +"entr'accrochiez", +"entr'accrochions", +"entr'accrochons", +"entr'accrochâmes", +"entr'accrochât", +"entr'accrochâtes", +"entr'accrochèrent", +"entr'accroché", +"entr'accrochées", +"entr'accrochés", +"entr'accusa", +"entr'accusaient", +"entr'accusait", +"entr'accusant", +"entr'accusassent", +"entr'accusassiez", +"entr'accusassions", +"entr'accuse", +"entr'accusent", +"entr'accuser", +"entr'accusera", +"entr'accuseraient", +"entr'accuserait", +"entr'accuserez", +"entr'accuseriez", +"entr'accuserions", +"entr'accuserons", +"entr'accuseront", +"entr'accusez", +"entr'accusiez", +"entr'accusions", +"entr'accusons", +"entr'accusâmes", +"entr'accusât", +"entr'accusâtes", +"entr'accusèrent", +"entr'accusé", +"entr'accusées", +"entr'accusés", +"entr'acte", +"entr'actes", +"entr'adapta", +"entr'adaptaient", +"entr'adaptait", +"entr'adaptant", +"entr'adaptassent", +"entr'adaptassiez", +"entr'adaptassions", +"entr'adapte", +"entr'adaptent", +"entr'adapter", +"entr'adaptera", +"entr'adapteraient", +"entr'adapterait", +"entr'adapterez", +"entr'adapteriez", +"entr'adapterions", +"entr'adapterons", +"entr'adapteront", +"entr'adaptez", +"entr'adaptiez", +"entr'adaptions", +"entr'adaptons", +"entr'adaptâmes", +"entr'adaptât", +"entr'adaptâtes", +"entr'adaptèrent", +"entr'adapté", +"entr'adaptées", +"entr'adaptés", +"entr'admira", +"entr'admirai", +"entr'admiraient", +"entr'admirais", +"entr'admirait", +"entr'admirant", +"entr'admiras", +"entr'admirasse", +"entr'admirassent", +"entr'admirasses", +"entr'admirassiez", +"entr'admirassions", +"entr'admire", +"entr'admirent", +"entr'admirer", +"entr'admirera", +"entr'admirerai", +"entr'admireraient", +"entr'admirerais", +"entr'admirerait", +"entr'admireras", +"entr'admirerez", +"entr'admireriez", +"entr'admirerions", +"entr'admirerons", +"entr'admireront", +"entr'admires", +"entr'admirez", +"entr'admiriez", +"entr'admirions", +"entr'admirons", +"entr'admirâmes", +"entr'admirât", +"entr'admirâtes", +"entr'admirèrent", +"entr'admiré", +"entr'admirée", +"entr'admirées", +"entr'admirés", +"entr'admonesta", +"entr'admonestaient", +"entr'admonestait", +"entr'admonestant", +"entr'admonestassent", +"entr'admonestassiez", +"entr'admonestassions", +"entr'admoneste", +"entr'admonestent", +"entr'admonester", +"entr'admonestera", +"entr'admonesteraient", +"entr'admonesterait", +"entr'admonesterez", +"entr'admonesteriez", +"entr'admonesterions", +"entr'admonesterons", +"entr'admonesteront", +"entr'admonestez", +"entr'admonestiez", +"entr'admonestions", +"entr'admonestons", +"entr'admonestâmes", +"entr'admonestât", +"entr'admonestâtes", +"entr'admonestèrent", +"entr'admonesté", +"entr'admonestées", +"entr'admonestés", +"entr'adressa", +"entr'adressaient", +"entr'adressait", +"entr'adressant", +"entr'adressassent", +"entr'adressassiez", +"entr'adressassions", +"entr'adresse", +"entr'adressent", +"entr'adresser", +"entr'adressera", +"entr'adresseraient", +"entr'adresserait", +"entr'adresserez", +"entr'adresseriez", +"entr'adresserions", +"entr'adresserons", +"entr'adresseront", +"entr'adressez", +"entr'adressiez", +"entr'adressions", +"entr'adressons", +"entr'adressâmes", +"entr'adressât", +"entr'adressâtes", +"entr'adressèrent", +"entr'adressé", +"entr'adressées", +"entr'adressés", +"entr'affronta", +"entr'affrontaient", +"entr'affrontait", +"entr'affrontant", +"entr'affrontassent", +"entr'affrontassiez", +"entr'affrontassions", +"entr'affronte", +"entr'affrontent", +"entr'affronter", +"entr'affrontera", +"entr'affronteraient", +"entr'affronterait", +"entr'affronterez", +"entr'affronteriez", +"entr'affronterions", +"entr'affronterons", +"entr'affronteront", +"entr'affrontez", +"entr'affrontiez", +"entr'affrontions", +"entr'affrontons", +"entr'affrontâmes", +"entr'affrontât", +"entr'affrontâtes", +"entr'affrontèrent", +"entr'affronté", +"entr'affrontées", +"entr'affrontés", +"entr'aida", +"entr'aidaient", +"entr'aidait", +"entr'aidant", +"entr'aidassent", +"entr'aidassiez", +"entr'aidassions", +"entr'aide", +"entr'aident", +"entr'aider", +"entr'aidera", +"entr'aideraient", +"entr'aiderait", +"entr'aiderez", +"entr'aideriez", +"entr'aiderions", +"entr'aiderons", +"entr'aideront", +"entr'aides", +"entr'aidez", +"entr'aidiez", +"entr'aidions", +"entr'aidons", +"entr'aidâmes", +"entr'aidât", +"entr'aidâtes", +"entr'aidèrent", +"entr'aidé", +"entr'aidées", +"entr'aidés", +"entr'aiguisa", +"entr'aiguisaient", +"entr'aiguisait", +"entr'aiguisant", +"entr'aiguisassent", +"entr'aiguisassiez", +"entr'aiguisassions", +"entr'aiguise", +"entr'aiguisent", +"entr'aiguiser", +"entr'aiguisera", +"entr'aiguiseraient", +"entr'aiguiserait", +"entr'aiguiserez", +"entr'aiguiseriez", +"entr'aiguiserions", +"entr'aiguiserons", +"entr'aiguiseront", +"entr'aiguisez", +"entr'aiguisiez", +"entr'aiguisions", +"entr'aiguisons", +"entr'aiguisâmes", +"entr'aiguisât", +"entr'aiguisâtes", +"entr'aiguisèrent", +"entr'aiguisé", +"entr'aiguisées", +"entr'aiguisés", +"entr'aima", +"entr'aimai", +"entr'aimaient", +"entr'aimais", +"entr'aimait", +"entr'aimant", +"entr'aimas", +"entr'aimasse", +"entr'aimassent", +"entr'aimasses", +"entr'aimassiez", +"entr'aimassions", +"entr'aime", +"entr'aiment", +"entr'aimer", +"entr'aimera", +"entr'aimerai", +"entr'aimeraient", +"entr'aimerais", +"entr'aimerait", +"entr'aimeras", +"entr'aimerez", +"entr'aimeriez", +"entr'aimerions", +"entr'aimerons", +"entr'aimeront", +"entr'aimes", +"entr'aimez", +"entr'aimiez", +"entr'aimions", +"entr'aimons", +"entr'aimâmes", +"entr'aimât", +"entr'aimâtes", +"entr'aimèrent", +"entr'aimé", +"entr'aimée", +"entr'aimées", +"entr'aimés", +"entr'anima", +"entr'animaient", +"entr'animait", +"entr'animant", +"entr'animassent", +"entr'animassiez", +"entr'animassions", +"entr'anime", +"entr'animent", +"entr'animer", +"entr'animera", +"entr'animeraient", +"entr'animerait", +"entr'animerez", +"entr'animeriez", +"entr'animerions", +"entr'animerons", +"entr'animeront", +"entr'animez", +"entr'animiez", +"entr'animions", +"entr'animons", +"entr'animâmes", +"entr'animât", +"entr'animâtes", +"entr'animèrent", +"entr'animé", +"entr'animées", +"entr'animés", +"entr'apercevaient", +"entr'apercevais", +"entr'apercevait", +"entr'apercevant", +"entr'apercevez", +"entr'aperceviez", +"entr'apercevions", +"entr'apercevoir", +"entr'apercevons", +"entr'apercevra", +"entr'apercevrai", +"entr'apercevraient", +"entr'apercevrais", +"entr'apercevrait", +"entr'apercevras", +"entr'apercevrez", +"entr'apercevriez", +"entr'apercevrions", +"entr'apercevrons", +"entr'apercevront", +"entr'aperçois", +"entr'aperçoit", +"entr'aperçoive", +"entr'aperçoivent", +"entr'aperçoives", +"entr'aperçu", +"entr'aperçue", +"entr'aperçues", +"entr'aperçurent", +"entr'aperçus", +"entr'aperçusse", +"entr'aperçussent", +"entr'aperçusses", +"entr'aperçussiez", +"entr'aperçussions", +"entr'aperçut", +"entr'aperçûmes", +"entr'aperçût", +"entr'aperçûtes", +"entr'apparais", +"entr'apparaissaient", +"entr'apparaissais", +"entr'apparaissait", +"entr'apparaissant", +"entr'apparaisse", +"entr'apparaissent", +"entr'apparaisses", +"entr'apparaissez", +"entr'apparaissiez", +"entr'apparaissions", +"entr'apparaissons", +"entr'apparait", +"entr'apparaitra", +"entr'apparaitrai", +"entr'apparaitraient", +"entr'apparaitrais", +"entr'apparaitrait", +"entr'apparaitras", +"entr'apparaitre", +"entr'apparaitrez", +"entr'apparaitriez", +"entr'apparaitrions", +"entr'apparaitrons", +"entr'apparaitront", +"entr'apparaît", +"entr'apparaîtra", +"entr'apparaîtrai", +"entr'apparaîtraient", +"entr'apparaîtrais", +"entr'apparaîtrait", +"entr'apparaîtras", +"entr'apparaître", +"entr'apparaîtrez", +"entr'apparaîtriez", +"entr'apparaîtrions", +"entr'apparaîtrons", +"entr'apparaîtront", +"entr'apparu", +"entr'apparue", +"entr'apparues", +"entr'apparurent", +"entr'apparus", +"entr'apparusse", +"entr'apparussent", +"entr'apparusses", +"entr'apparussiez", +"entr'apparussions", +"entr'apparut", +"entr'apparûmes", +"entr'apparût", +"entr'apparûtes", +"entr'appela", +"entr'appelaient", +"entr'appelait", +"entr'appelant", +"entr'appelassent", +"entr'appelassiez", +"entr'appelassions", +"entr'appeler", +"entr'appelez", +"entr'appeliez", +"entr'appelions", +"entr'appelle", +"entr'appellent", +"entr'appellera", +"entr'appelleraient", +"entr'appellerait", +"entr'appellerez", +"entr'appelleriez", +"entr'appellerions", +"entr'appellerons", +"entr'appelleront", +"entr'appelles", +"entr'appelons", +"entr'appelâmes", +"entr'appelât", +"entr'appelâtes", +"entr'appelèrent", +"entr'appelé", +"entr'appelées", +"entr'appelés", +"entr'apprenaient", +"entr'apprenait", +"entr'apprenant", +"entr'apprend", +"entr'apprendra", +"entr'apprendraient", +"entr'apprendrait", +"entr'apprendre", +"entr'apprendriez", +"entr'apprendrions", +"entr'apprendrons", +"entr'apprendront", +"entr'apprenez", +"entr'appreniez", +"entr'apprenions", +"entr'apprenne", +"entr'apprennent", +"entr'apprennes", +"entr'apprenons", +"entr'apprirent", +"entr'appris", +"entr'apprise", +"entr'apprises", +"entr'apprissent", +"entr'apprissiez", +"entr'apprissions", +"entr'apprit", +"entr'approcha", +"entr'approchaient", +"entr'approchait", +"entr'approchant", +"entr'approchassent", +"entr'approchassiez", +"entr'approchassions", +"entr'approche", +"entr'approchent", +"entr'approcher", +"entr'approchera", +"entr'approcheraient", +"entr'approcherait", +"entr'approcherez", +"entr'approcheriez", +"entr'approcherions", +"entr'approcherons", +"entr'approcheront", +"entr'approchez", +"entr'approchiez", +"entr'approchions", +"entr'approchons", +"entr'approchâmes", +"entr'approchât", +"entr'approchâtes", +"entr'approchèrent", +"entr'approché", +"entr'approchées", +"entr'approchés", +"entr'apprîmes", +"entr'apprît", +"entr'apprîtes", +"entr'arquebusa", +"entr'arquebusaient", +"entr'arquebusait", +"entr'arquebusant", +"entr'arquebusassent", +"entr'arquebusassiez", +"entr'arquebusassions", +"entr'arquebuse", +"entr'arquebusent", +"entr'arquebuser", +"entr'arquebusera", +"entr'arquebuseraient", +"entr'arquebuserait", +"entr'arquebuserez", +"entr'arquebuseriez", +"entr'arquebuserions", +"entr'arquebuserons", +"entr'arquebuseront", +"entr'arquebusez", +"entr'arquebusiez", +"entr'arquebusions", +"entr'arquebusons", +"entr'arquebusâmes", +"entr'arquebusât", +"entr'arquebusâtes", +"entr'arquebusèrent", +"entr'arquebusé", +"entr'arquebusées", +"entr'arquebusés", +"entr'assassina", +"entr'assassinaient", +"entr'assassinait", +"entr'assassinant", +"entr'assassinassent", +"entr'assassinassiez", +"entr'assassinassions", +"entr'assassine", +"entr'assassinent", +"entr'assassiner", +"entr'assassinera", +"entr'assassineraient", +"entr'assassinerait", +"entr'assassinerez", +"entr'assassineriez", +"entr'assassinerions", +"entr'assassinerons", +"entr'assassineront", +"entr'assassinez", +"entr'assassiniez", +"entr'assassinions", +"entr'assassinons", +"entr'assassinâmes", +"entr'assassinât", +"entr'assassinâtes", +"entr'assassinèrent", +"entr'assassiné", +"entr'assassinées", +"entr'assassinés", +"entr'assigna", +"entr'assignaient", +"entr'assignait", +"entr'assignant", +"entr'assignassent", +"entr'assignassiez", +"entr'assignassions", +"entr'assigne", +"entr'assignent", +"entr'assigner", +"entr'assignera", +"entr'assigneraient", +"entr'assignerait", +"entr'assignerez", +"entr'assigneriez", +"entr'assignerions", +"entr'assignerons", +"entr'assigneront", +"entr'assignez", +"entr'assigniez", +"entr'assignions", +"entr'assignons", +"entr'assignâmes", +"entr'assignât", +"entr'assignâtes", +"entr'assignèrent", +"entr'assigné", +"entr'assignées", +"entr'assignés", +"entr'assomma", +"entr'assommaient", +"entr'assommait", +"entr'assommant", +"entr'assommassent", +"entr'assommassiez", +"entr'assommassions", +"entr'assomme", +"entr'assomment", +"entr'assommer", +"entr'assommera", +"entr'assommeraient", +"entr'assommerait", +"entr'assommerez", +"entr'assommeriez", +"entr'assommerions", +"entr'assommerons", +"entr'assommeront", +"entr'assommez", +"entr'assommiez", +"entr'assommions", +"entr'assommons", +"entr'assommâmes", +"entr'assommât", +"entr'assommâtes", +"entr'assommèrent", +"entr'assommé", +"entr'assommées", +"entr'assommés", +"entr'attaqua", +"entr'attaquaient", +"entr'attaquait", +"entr'attaquant", +"entr'attaquassent", +"entr'attaquassiez", +"entr'attaquassions", +"entr'attaque", +"entr'attaquent", +"entr'attaquer", +"entr'attaquera", +"entr'attaqueraient", +"entr'attaquerait", +"entr'attaquerez", +"entr'attaqueriez", +"entr'attaquerions", +"entr'attaquerons", +"entr'attaqueront", +"entr'attaquez", +"entr'attaquiez", +"entr'attaquions", +"entr'attaquons", +"entr'attaquâmes", +"entr'attaquât", +"entr'attaquâtes", +"entr'attaquèrent", +"entr'attaqué", +"entr'attaquées", +"entr'attaqués", +"entr'attend", +"entr'attendaient", +"entr'attendait", +"entr'attendant", +"entr'attende", +"entr'attendent", +"entr'attendez", +"entr'attendiez", +"entr'attendions", +"entr'attendirent", +"entr'attendissent", +"entr'attendissiez", +"entr'attendissions", +"entr'attendit", +"entr'attendons", +"entr'attendra", +"entr'attendraient", +"entr'attendrait", +"entr'attendre", +"entr'attendrez", +"entr'attendriez", +"entr'attendrions", +"entr'attendrons", +"entr'attendront", +"entr'attendu", +"entr'attendue", +"entr'attendues", +"entr'attendus", +"entr'attendîmes", +"entr'attendît", +"entr'attendîtes", +"entr'autres", +"entr'averti", +"entr'averties", +"entr'avertir", +"entr'avertira", +"entr'avertiraient", +"entr'avertirait", +"entr'avertirent", +"entr'avertirez", +"entr'avertiriez", +"entr'avertirions", +"entr'avertirons", +"entr'avertiront", +"entr'avertis", +"entr'avertissaient", +"entr'avertissait", +"entr'avertissant", +"entr'avertisse", +"entr'avertissent", +"entr'avertissez", +"entr'avertissiez", +"entr'avertissions", +"entr'avertissons", +"entr'avertit", +"entr'avertîmes", +"entr'avertît", +"entr'avertîtes", +"entr'avoua", +"entr'avouaient", +"entr'avouait", +"entr'avouant", +"entr'avouassent", +"entr'avouassiez", +"entr'avouassions", +"entr'avoue", +"entr'avouent", +"entr'avouer", +"entr'avouera", +"entr'avoueraient", +"entr'avouerait", +"entr'avouerez", +"entr'avoueriez", +"entr'avouerions", +"entr'avouerons", +"entr'avoueront", +"entr'avouez", +"entr'avouiez", +"entr'avouions", +"entr'avouons", +"entr'avouâmes", +"entr'avouât", +"entr'avouâtes", +"entr'avouèrent", +"entr'avoué", +"entr'avouées", +"entr'avoués", +"entr'axe", +"entr'axes", +"entr'embarrassa", +"entr'embarrassaient", +"entr'embarrassait", +"entr'embarrassant", +"entr'embarrassassent", +"entr'embarrassassiez", +"entr'embarrassassions", +"entr'embarrasse", +"entr'embarrassent", +"entr'embarrasser", +"entr'embarrassera", +"entr'embarrasseraient", +"entr'embarrasserait", +"entr'embarrasserez", +"entr'embarrasseriez", +"entr'embarrasserions", +"entr'embarrasserons", +"entr'embarrasseront", +"entr'embarrassez", +"entr'embarrassiez", +"entr'embarrassions", +"entr'embarrassons", +"entr'embarrassâmes", +"entr'embarrassât", +"entr'embarrassâtes", +"entr'embarrassèrent", +"entr'embarrassé", +"entr'embarrassées", +"entr'embarrassés", +"entr'embrassa", +"entr'embrassaient", +"entr'embrassait", +"entr'embrassant", +"entr'embrassassent", +"entr'embrassassiez", +"entr'embrassassions", +"entr'embrasse", +"entr'embrassent", +"entr'embrasser", +"entr'embrassera", +"entr'embrasseraient", +"entr'embrasserait", +"entr'embrasserez", +"entr'embrasseriez", +"entr'embrasserions", +"entr'embrasserons", +"entr'embrasseront", +"entr'embrassez", +"entr'embrassiez", +"entr'embrassions", +"entr'embrassons", +"entr'embrassâmes", +"entr'embrassât", +"entr'embrassâtes", +"entr'embrassèrent", +"entr'embrassé", +"entr'embrassées", +"entr'embrassés", +"entr'empêcha", +"entr'empêchaient", +"entr'empêchait", +"entr'empêchant", +"entr'empêchassent", +"entr'empêchassiez", +"entr'empêchassions", +"entr'empêche", +"entr'empêchent", +"entr'empêcher", +"entr'empêchera", +"entr'empêcheraient", +"entr'empêcherait", +"entr'empêcherez", +"entr'empêcheriez", +"entr'empêcherions", +"entr'empêcherons", +"entr'empêcheront", +"entr'empêchez", +"entr'empêchiez", +"entr'empêchions", +"entr'empêchons", +"entr'empêchâmes", +"entr'empêchât", +"entr'empêchâtes", +"entr'empêchèrent", +"entr'empêché", +"entr'empêchées", +"entr'empêchés", +"entr'encourage", +"entr'encouragea", +"entr'encourageaient", +"entr'encourageait", +"entr'encourageant", +"entr'encourageassent", +"entr'encourageassiez", +"entr'encourageassions", +"entr'encouragent", +"entr'encourageons", +"entr'encourager", +"entr'encouragera", +"entr'encourageraient", +"entr'encouragerait", +"entr'encouragerez", +"entr'encourageriez", +"entr'encouragerions", +"entr'encouragerons", +"entr'encourageront", +"entr'encouragez", +"entr'encourageâmes", +"entr'encourageât", +"entr'encourageâtes", +"entr'encouragiez", +"entr'encouragions", +"entr'encouragèrent", +"entr'encouragé", +"entr'encouragées", +"entr'encouragés", +"entr'enleva", +"entr'enlevaient", +"entr'enlevait", +"entr'enlevant", +"entr'enlevassent", +"entr'enlevassiez", +"entr'enlevassions", +"entr'enlever", +"entr'enlevez", +"entr'enleviez", +"entr'enlevions", +"entr'enlevons", +"entr'enlevâmes", +"entr'enlevât", +"entr'enlevâtes", +"entr'enlevèrent", +"entr'enlevé", +"entr'enlevées", +"entr'enlevés", +"entr'enlève", +"entr'enlèvent", +"entr'enlèvera", +"entr'enlèveraient", +"entr'enlèverait", +"entr'enlèverez", +"entr'enlèveriez", +"entr'enlèverions", +"entr'enlèverons", +"entr'enlèveront", +"entr'entend", +"entr'entendaient", +"entr'entendait", +"entr'entendant", +"entr'entende", +"entr'entendent", +"entr'entendez", +"entr'entendiez", +"entr'entendions", +"entr'entendirent", +"entr'entendissent", +"entr'entendissiez", +"entr'entendissions", +"entr'entendit", +"entr'entendons", +"entr'entendra", +"entr'entendraient", +"entr'entendrait", +"entr'entendre", +"entr'entendrez", +"entr'entendriez", +"entr'entendrions", +"entr'entendrons", +"entr'entendront", +"entr'entendu", +"entr'entendue", +"entr'entendues", +"entr'entendus", +"entr'entendîmes", +"entr'entendît", +"entr'entendîtes", +"entr'enverra", +"entr'enverrai", +"entr'enverraient", +"entr'enverrais", +"entr'enverrait", +"entr'enverras", +"entr'enverrez", +"entr'enverriez", +"entr'enverrions", +"entr'enverrons", +"entr'enverront", +"entr'envoie", +"entr'envoient", +"entr'envoies", +"entr'envoya", +"entr'envoyai", +"entr'envoyaient", +"entr'envoyais", +"entr'envoyait", +"entr'envoyant", +"entr'envoyas", +"entr'envoyasse", +"entr'envoyassent", +"entr'envoyasses", +"entr'envoyassiez", +"entr'envoyassions", +"entr'envoyer", +"entr'envoyez", +"entr'envoyiez", +"entr'envoyions", +"entr'envoyons", +"entr'envoyâmes", +"entr'envoyât", +"entr'envoyâtes", +"entr'envoyèrent", +"entr'envoyé", +"entr'envoyée", +"entr'envoyées", +"entr'envoyés", +"entr'escroqua", +"entr'escroquaient", +"entr'escroquait", +"entr'escroquant", +"entr'escroquassent", +"entr'escroquassiez", +"entr'escroquassions", +"entr'escroque", +"entr'escroquent", +"entr'escroquer", +"entr'escroquera", +"entr'escroqueraient", +"entr'escroquerait", +"entr'escroquerez", +"entr'escroqueriez", +"entr'escroquerions", +"entr'escroquerons", +"entr'escroqueront", +"entr'escroquez", +"entr'escroquiez", +"entr'escroquions", +"entr'escroquons", +"entr'escroquâmes", +"entr'escroquât", +"entr'escroquâtes", +"entr'escroquèrent", +"entr'escroqué", +"entr'escroquées", +"entr'escroqués", +"entr'eux", +"entr'excita", +"entr'excitaient", +"entr'excitait", +"entr'excitant", +"entr'excitassent", +"entr'excitassiez", +"entr'excitassions", +"entr'excite", +"entr'excitent", +"entr'exciter", +"entr'excitera", +"entr'exciteraient", +"entr'exciterait", +"entr'exciterez", +"entr'exciteriez", +"entr'exciterions", +"entr'exciterons", +"entr'exciteront", +"entr'excitez", +"entr'excitiez", +"entr'excitions", +"entr'excitons", +"entr'excitâmes", +"entr'excitât", +"entr'excitâtes", +"entr'excitèrent", +"entr'excité", +"entr'excitées", +"entr'excités", +"entr'exhorta", +"entr'exhortaient", +"entr'exhortait", +"entr'exhortant", +"entr'exhortassent", +"entr'exhortassiez", +"entr'exhortassions", +"entr'exhorte", +"entr'exhortent", +"entr'exhorter", +"entr'exhortera", +"entr'exhorteraient", +"entr'exhorterait", +"entr'exhorterez", +"entr'exhorteriez", +"entr'exhorterions", +"entr'exhorterons", +"entr'exhorteront", +"entr'exhortez", +"entr'exhortiez", +"entr'exhortions", +"entr'exhortons", +"entr'exhortâmes", +"entr'exhortât", +"entr'exhortâtes", +"entr'exhortèrent", +"entr'exhorté", +"entr'exhortées", +"entr'exhortés", +"entr'hiver", +"entr'hiverna", +"entr'hivernai", +"entr'hivernaient", +"entr'hivernais", +"entr'hivernait", +"entr'hivernant", +"entr'hivernas", +"entr'hivernasse", +"entr'hivernassent", +"entr'hivernasses", +"entr'hivernassiez", +"entr'hivernassions", +"entr'hiverne", +"entr'hivernent", +"entr'hiverner", +"entr'hivernera", +"entr'hivernerai", +"entr'hiverneraient", +"entr'hivernerais", +"entr'hivernerait", +"entr'hiverneras", +"entr'hivernerez", +"entr'hiverneriez", +"entr'hivernerions", +"entr'hivernerons", +"entr'hiverneront", +"entr'hivernes", +"entr'hivernez", +"entr'hiverniez", +"entr'hivernions", +"entr'hivernons", +"entr'hivernâmes", +"entr'hivernât", +"entr'hivernâtes", +"entr'hivernèrent", +"entr'hiverné", +"entr'hivernée", +"entr'hivernées", +"entr'hivernés", +"entr'honora", +"entr'honoraient", +"entr'honorait", +"entr'honorant", +"entr'honorassent", +"entr'honorassiez", +"entr'honorassions", +"entr'honore", +"entr'honorent", +"entr'honorer", +"entr'honorera", +"entr'honoreraient", +"entr'honorerait", +"entr'honorerez", +"entr'honoreriez", +"entr'honorerions", +"entr'honorerons", +"entr'honoreront", +"entr'honorez", +"entr'honoriez", +"entr'honorions", +"entr'honorons", +"entr'honorâmes", +"entr'honorât", +"entr'honorâtes", +"entr'honorèrent", +"entr'honoré", +"entr'honorées", +"entr'honorés", +"entr'immola", +"entr'immolaient", +"entr'immolait", +"entr'immolant", +"entr'immolassent", +"entr'immolassiez", +"entr'immolassions", +"entr'immole", +"entr'immolent", +"entr'immoler", +"entr'immolera", +"entr'immoleraient", +"entr'immolerait", +"entr'immolerez", +"entr'immoleriez", +"entr'immolerions", +"entr'immolerons", +"entr'immoleront", +"entr'immolez", +"entr'immoliez", +"entr'immolions", +"entr'immolons", +"entr'immolâmes", +"entr'immolât", +"entr'immolâtes", +"entr'immolèrent", +"entr'immolé", +"entr'immolées", +"entr'immolés", +"entr'incommoda", +"entr'incommodaient", +"entr'incommodait", +"entr'incommodant", +"entr'incommodassent", +"entr'incommodassiez", +"entr'incommodassions", +"entr'incommode", +"entr'incommodent", +"entr'incommoder", +"entr'incommodera", +"entr'incommoderaient", +"entr'incommoderait", +"entr'incommoderez", +"entr'incommoderiez", +"entr'incommoderions", +"entr'incommoderons", +"entr'incommoderont", +"entr'incommodez", +"entr'incommodiez", +"entr'incommodions", +"entr'incommodons", +"entr'incommodâmes", +"entr'incommodât", +"entr'incommodâtes", +"entr'incommodèrent", +"entr'incommodé", +"entr'incommodées", +"entr'incommodés", +"entr'injuria", +"entr'injuriaient", +"entr'injuriait", +"entr'injuriant", +"entr'injuriassent", +"entr'injuriassiez", +"entr'injuriassions", +"entr'injurie", +"entr'injurient", +"entr'injurier", +"entr'injuriera", +"entr'injurieraient", +"entr'injurierait", +"entr'injurierez", +"entr'injurieriez", +"entr'injurierions", +"entr'injurierons", +"entr'injurieront", +"entr'injuriez", +"entr'injuriiez", +"entr'injuriions", +"entr'injurions", +"entr'injuriâmes", +"entr'injuriât", +"entr'injuriâtes", +"entr'injurièrent", +"entr'injurié", +"entr'injuriées", +"entr'injuriés", +"entr'instruira", +"entr'instruiraient", +"entr'instruirait", +"entr'instruire", +"entr'instruirez", +"entr'instruiriez", +"entr'instruirions", +"entr'instruirons", +"entr'instruiront", +"entr'instruisaient", +"entr'instruisait", +"entr'instruisant", +"entr'instruise", +"entr'instruisent", +"entr'instruisez", +"entr'instruisiez", +"entr'instruisions", +"entr'instruisirent", +"entr'instruisissent", +"entr'instruisissions", +"entr'instruisit", +"entr'instruisons", +"entr'instruisîmes", +"entr'instruisît", +"entr'instruisîtes", +"entr'instruit", +"entr'instruite", +"entr'instruites", +"entr'instruits", +"entr'oblige", +"entr'obligea", +"entr'obligeaient", +"entr'obligeait", +"entr'obligeant", +"entr'obligeassent", +"entr'obligeassiez", +"entr'obligeassions", +"entr'obligent", +"entr'obligeons", +"entr'obliger", +"entr'obligera", +"entr'obligeraient", +"entr'obligerait", +"entr'obligerez", +"entr'obligeriez", +"entr'obligerions", +"entr'obligerons", +"entr'obligeront", +"entr'obligez", +"entr'obligeâmes", +"entr'obligeât", +"entr'obligeâtes", +"entr'obligiez", +"entr'obligions", +"entr'obligèrent", +"entr'obligé", +"entr'obligées", +"entr'obligés", +"entr'offensa", +"entr'offensaient", +"entr'offensait", +"entr'offensant", +"entr'offensassent", +"entr'offensassiez", +"entr'offensassions", +"entr'offense", +"entr'offensent", +"entr'offenser", +"entr'offensera", +"entr'offenseraient", +"entr'offenserait", +"entr'offenserez", +"entr'offenseriez", +"entr'offenserions", +"entr'offenserons", +"entr'offenseront", +"entr'offensez", +"entr'offensiez", +"entr'offensions", +"entr'offensons", +"entr'offensâmes", +"entr'offensât", +"entr'offensâtes", +"entr'offensèrent", +"entr'offensé", +"entr'offensées", +"entr'offensés", +"entr'oie", +"entr'oient", +"entr'oies", +"entr'ois", +"entr'oit", +"entr'ombrage", +"entr'ombragea", +"entr'ombrageaient", +"entr'ombrageait", +"entr'ombrageant", +"entr'ombrageassent", +"entr'ombrageassiez", +"entr'ombrageassions", +"entr'ombragent", +"entr'ombrageons", +"entr'ombrager", +"entr'ombragera", +"entr'ombrageraient", +"entr'ombragerait", +"entr'ombragerez", +"entr'ombrageriez", +"entr'ombragerions", +"entr'ombragerons", +"entr'ombrageront", +"entr'ombragez", +"entr'ombrageâmes", +"entr'ombrageât", +"entr'ombrageâtes", +"entr'ombragiez", +"entr'ombragions", +"entr'ombragèrent", +"entr'ombragé", +"entr'ombragées", +"entr'ombragés", +"entr'opercule", +"entr'orraient", +"entr'orrais", +"entr'orrait", +"entr'orriez", +"entr'orrions", +"entr'oublia", +"entr'oubliaient", +"entr'oubliait", +"entr'oubliant", +"entr'oubliassent", +"entr'oubliassiez", +"entr'oubliassions", +"entr'oublie", +"entr'oublient", +"entr'oublier", +"entr'oubliera", +"entr'oublieraient", +"entr'oublierait", +"entr'oublierez", +"entr'oublieriez", +"entr'oublierions", +"entr'oublierons", +"entr'oublieront", +"entr'oubliez", +"entr'oubliiez", +"entr'oubliions", +"entr'oublions", +"entr'oubliâmes", +"entr'oubliât", +"entr'oubliâtes", +"entr'oublièrent", +"entr'oublié", +"entr'oubliées", +"entr'oubliés", +"entr'outrage", +"entr'outragea", +"entr'outrageaient", +"entr'outrageait", +"entr'outrageant", +"entr'outrageassent", +"entr'outrageassiez", +"entr'outrageassions", +"entr'outragent", +"entr'outrageons", +"entr'outrager", +"entr'outragera", +"entr'outrageraient", +"entr'outragerait", +"entr'outragerez", +"entr'outrageriez", +"entr'outragerions", +"entr'outragerons", +"entr'outrageront", +"entr'outragez", +"entr'outrageâmes", +"entr'outrageât", +"entr'outrageâtes", +"entr'outragiez", +"entr'outragions", +"entr'outragèrent", +"entr'outragé", +"entr'outragées", +"entr'outragés", +"entr'ouvert", +"entr'ouverte", +"entr'ouvertes", +"entr'ouverts", +"entr'ouverture", +"entr'ouvraient", +"entr'ouvrais", +"entr'ouvrait", +"entr'ouvrant", +"entr'ouvre", +"entr'ouvrent", +"entr'ouvres", +"entr'ouvrez", +"entr'ouvriez", +"entr'ouvrions", +"entr'ouvrir", +"entr'ouvrira", +"entr'ouvrirai", +"entr'ouvriraient", +"entr'ouvrirais", +"entr'ouvrirait", +"entr'ouvriras", +"entr'ouvrirent", +"entr'ouvrirez", +"entr'ouvririez", +"entr'ouvririons", +"entr'ouvrirons", +"entr'ouvriront", +"entr'ouvris", +"entr'ouvrisse", +"entr'ouvrissent", +"entr'ouvrisses", +"entr'ouvrissiez", +"entr'ouvrissions", +"entr'ouvrit", +"entr'ouvrons", +"entr'ouvrîmes", +"entr'ouvrît", +"entr'ouvrîtes", +"entr'ouï", +"entr'ouïe", +"entr'ouïes", +"entr'ouïmes", +"entr'ouïr", +"entr'ouïra", +"entr'ouïrai", +"entr'ouïraient", +"entr'ouïrais", +"entr'ouïrait", +"entr'ouïras", +"entr'ouïrent", +"entr'ouïrez", +"entr'ouïriez", +"entr'ouïrions", +"entr'ouïrons", +"entr'ouïront", +"entr'ouïs", +"entr'ouïsse", +"entr'ouïssent", +"entr'ouïsses", +"entr'ouïssiez", +"entr'ouïssions", +"entr'ouït", +"entr'ouïtes", +"entr'oyaient", +"entr'oyais", +"entr'oyait", +"entr'oyant", +"entr'oyez", +"entr'oyiez", +"entr'oyions", +"entr'oyons", +"entr'usa", +"entr'usaient", +"entr'usait", +"entr'usant", +"entr'usassent", +"entr'usassiez", +"entr'usassions", +"entr'use", +"entr'usent", +"entr'user", +"entr'usera", +"entr'useraient", +"entr'userait", +"entr'userez", +"entr'useriez", +"entr'userions", +"entr'userons", +"entr'useront", +"entr'usez", +"entr'usiez", +"entr'usions", +"entr'usons", +"entr'usâmes", +"entr'usât", +"entr'usâtes", +"entr'usèrent", +"entr'usé", +"entr'usées", +"entr'usés", +"entr'ébranla", +"entr'ébranlaient", +"entr'ébranlait", +"entr'ébranlant", +"entr'ébranlassent", +"entr'ébranlassiez", +"entr'ébranlassions", +"entr'ébranle", +"entr'ébranlent", +"entr'ébranler", +"entr'ébranlera", +"entr'ébranleraient", +"entr'ébranlerait", +"entr'ébranlerez", +"entr'ébranleriez", +"entr'ébranlerions", +"entr'ébranlerons", +"entr'ébranleront", +"entr'ébranlez", +"entr'ébranliez", +"entr'ébranlions", +"entr'ébranlons", +"entr'ébranlâmes", +"entr'ébranlât", +"entr'ébranlâtes", +"entr'ébranlèrent", +"entr'ébranlé", +"entr'ébranlées", +"entr'ébranlés", +"entr'éclairci", +"entr'éclaircies", +"entr'éclaircir", +"entr'éclaircira", +"entr'éclairciraient", +"entr'éclaircirait", +"entr'éclaircirent", +"entr'éclaircirez", +"entr'éclairciriez", +"entr'éclaircirions", +"entr'éclaircirons", +"entr'éclairciront", +"entr'éclaircis", +"entr'éclaircissaient", +"entr'éclaircissait", +"entr'éclaircissant", +"entr'éclaircisse", +"entr'éclaircissent", +"entr'éclaircissez", +"entr'éclaircissiez", +"entr'éclaircissions", +"entr'éclaircissons", +"entr'éclaircit", +"entr'éclaircîmes", +"entr'éclaircît", +"entr'éclaircîtes", +"entr'éclore", +"entr'éclose", +"entr'écouta", +"entr'écoutaient", +"entr'écoutait", +"entr'écoutant", +"entr'écoutassent", +"entr'écoutassiez", +"entr'écoutassions", +"entr'écoute", +"entr'écoutent", +"entr'écouter", +"entr'écoutera", +"entr'écouteraient", +"entr'écouterait", +"entr'écouterez", +"entr'écouteriez", +"entr'écouterions", +"entr'écouterons", +"entr'écouteront", +"entr'écoutez", +"entr'écoutiez", +"entr'écoutions", +"entr'écoutons", +"entr'écoutâmes", +"entr'écoutât", +"entr'écoutâtes", +"entr'écoutèrent", +"entr'écouté", +"entr'écoutées", +"entr'écoutés", +"entr'écrasa", +"entr'écrasai", +"entr'écrasaient", +"entr'écrasais", +"entr'écrasait", +"entr'écrasant", +"entr'écrasas", +"entr'écrasasse", +"entr'écrasassent", +"entr'écrasasses", +"entr'écrasassiez", +"entr'écrasassions", +"entr'écrase", +"entr'écrasent", +"entr'écraser", +"entr'écrasera", +"entr'écraserai", +"entr'écraseraient", +"entr'écraserais", +"entr'écraserait", +"entr'écraseras", +"entr'écraserez", +"entr'écraseriez", +"entr'écraserions", +"entr'écraserons", +"entr'écraseront", +"entr'écrases", +"entr'écrasez", +"entr'écrasiez", +"entr'écrasions", +"entr'écrasons", +"entr'écrasâmes", +"entr'écrasât", +"entr'écrasâtes", +"entr'écrasèrent", +"entr'écrasé", +"entr'écrasée", +"entr'écrasées", +"entr'écrasés", +"entr'écrira", +"entr'écriraient", +"entr'écrirait", +"entr'écrire", +"entr'écrirez", +"entr'écririez", +"entr'écririons", +"entr'écrirons", +"entr'écriront", +"entr'écrit", +"entr'écrite", +"entr'écrites", +"entr'écrits", +"entr'écrivaient", +"entr'écrivait", +"entr'écrivant", +"entr'écrive", +"entr'écrivent", +"entr'écrivez", +"entr'écriviez", +"entr'écrivions", +"entr'écrivirent", +"entr'écrivissent", +"entr'écrivissions", +"entr'écrivit", +"entr'écrivons", +"entr'écrivîmes", +"entr'écrivît", +"entr'écrivîtes", +"entr'égorge", +"entr'égorgea", +"entr'égorgeai", +"entr'égorgeaient", +"entr'égorgeait", +"entr'égorgeant", +"entr'égorgeassent", +"entr'égorgeassiez", +"entr'égorgeassions", +"entr'égorgemens", +"entr'égorgement", +"entr'égorgements", +"entr'égorgent", +"entr'égorgeons", +"entr'égorger", +"entr'égorgera", +"entr'égorgeraient", +"entr'égorgerait", +"entr'égorgerez", +"entr'égorgeriez", +"entr'égorgerions", +"entr'égorgerons", +"entr'égorgeront", +"entr'égorges", +"entr'égorgez", +"entr'égorgeâmes", +"entr'égorgeât", +"entr'égorgeâtes", +"entr'égorgiez", +"entr'égorgions", +"entr'égorgèrent", +"entr'égorgé", +"entr'égorgée", +"entr'égorgées", +"entr'égorgés", +"entr'égratigna", +"entr'égratignaient", +"entr'égratignait", +"entr'égratignant", +"entr'égratignassent", +"entr'égratignassiez", +"entr'égratignassions", +"entr'égratigne", +"entr'égratignent", +"entr'égratigner", +"entr'égratignera", +"entr'égratigneraient", +"entr'égratignerait", +"entr'égratignerez", +"entr'égratigneriez", +"entr'égratignerions", +"entr'égratignerons", +"entr'égratigneront", +"entr'égratignez", +"entr'égratigniez", +"entr'égratignions", +"entr'égratignons", +"entr'égratignâmes", +"entr'égratignât", +"entr'égratignâtes", +"entr'égratignèrent", +"entr'égratigné", +"entr'égratignées", +"entr'égratignés", +"entr'épia", +"entr'épiaient", +"entr'épiait", +"entr'épiant", +"entr'épiassent", +"entr'épiassiez", +"entr'épiassions", +"entr'épie", +"entr'épient", +"entr'épier", +"entr'épiera", +"entr'épieraient", +"entr'épierait", +"entr'épierez", +"entr'épieriez", +"entr'épierions", +"entr'épierons", +"entr'épieront", +"entr'épiez", +"entr'épiiez", +"entr'épiions", +"entr'épions", +"entr'épiâmes", +"entr'épiât", +"entr'épiâtes", +"entr'épièrent", +"entr'épié", +"entr'épiées", +"entr'épiés", +"entr'éprouva", +"entr'éprouvaient", +"entr'éprouvait", +"entr'éprouvant", +"entr'éprouvassent", +"entr'éprouvassiez", +"entr'éprouvassions", +"entr'éprouve", +"entr'éprouvent", +"entr'éprouver", +"entr'éprouvera", +"entr'éprouveraient", +"entr'éprouverait", +"entr'éprouverez", +"entr'éprouveriez", +"entr'éprouverions", +"entr'éprouverons", +"entr'éprouveront", +"entr'éprouvez", +"entr'éprouviez", +"entr'éprouvions", +"entr'éprouvons", +"entr'éprouvâmes", +"entr'éprouvât", +"entr'éprouvâtes", +"entr'éprouvèrent", +"entr'éprouvé", +"entr'éprouvées", +"entr'éprouvés", +"entr'étouffa", +"entr'étouffaient", +"entr'étouffait", +"entr'étouffant", +"entr'étouffassent", +"entr'étouffassiez", +"entr'étouffassions", +"entr'étouffe", +"entr'étouffent", +"entr'étouffer", +"entr'étouffera", +"entr'étoufferaient", +"entr'étoufferait", +"entr'étoufferez", +"entr'étoufferiez", +"entr'étoufferions", +"entr'étoufferons", +"entr'étoufferont", +"entr'étouffez", +"entr'étouffiez", +"entr'étouffions", +"entr'étouffons", +"entr'étouffâmes", +"entr'étouffât", +"entr'étouffâtes", +"entr'étouffèrent", +"entr'étouffé", +"entr'étouffées", +"entr'étouffés", +"entr'étripa", +"entr'étripaient", +"entr'étripait", +"entr'étripant", +"entr'étripassent", +"entr'étripassiez", +"entr'étripassions", +"entr'étripe", +"entr'étripent", +"entr'étriper", +"entr'étripera", +"entr'étriperaient", +"entr'étriperait", +"entr'étriperez", +"entr'étriperiez", +"entr'étriperions", +"entr'étriperons", +"entr'étriperont", +"entr'étripez", +"entr'étripiez", +"entr'étripions", +"entr'étripons", +"entr'étripâmes", +"entr'étripât", +"entr'étripâtes", +"entr'étripèrent", +"entr'étripé", +"entr'étripées", +"entr'étripés", +"entr'éveilla", +"entr'éveillaient", +"entr'éveillait", +"entr'éveillant", +"entr'éveillassent", +"entr'éveillassiez", +"entr'éveillassions", +"entr'éveille", +"entr'éveillent", +"entr'éveiller", +"entr'éveillera", +"entr'éveilleraient", +"entr'éveillerait", +"entr'éveillerez", +"entr'éveilleriez", +"entr'éveillerions", +"entr'éveillerons", +"entr'éveilleront", +"entr'éveillez", +"entr'éveilliez", +"entr'éveillions", +"entr'éveillons", +"entr'éveillâmes", +"entr'éveillât", +"entr'éveillâtes", +"entr'éveillèrent", +"entr'éveillé", +"entr'éveillées", +"entr'éveillés", +"entrer-coucher", +"entrée-sortie", +"entrées-sorties", +"entéro-colite", +"entéro-colites", +"entéro-cystocèle", +"entéro-hydrocèle", +"entéro-hydromphale", +"entéro-hémorrhagie", +"entéro-mérocèle", +"entéro-mésentérite", +"entéro-pneumatose", +"entéro-rénal", +"entéro-rénale", +"entéro-rénales", +"entéro-rénaux", +"entéro-sarcocèle", +"entéro-sarcocèles", +"entéro-sténose", +"entéro-sténoses", +"entéro-épiplocèle", +"entéro-épiplocèles", +"ep's", +"eskimau-aléoute", +"eskimo-aléoute", +"eskimo-aléoutes", +"espace-boutique", +"espace-temps", +"espace-vente", +"espaces-temps", +"espaces-ventes", +"espadon-voilier", +"esprit-de-bois", +"esprit-de-sel", +"esprit-de-vin", +"esprit-fort", +"esprits-forts", +"esquimau-aléoute", +"esquimo-aléoute", +"essert-romanais", +"essert-romanaise", +"essert-romanaises", +"essuie-glace", +"essuie-glaces", +"essuie-main", +"essuie-mains", +"essuie-meuble", +"essuie-meubles", +"essuie-phare", +"essuie-phares", +"essuie-pied", +"essuie-pieds", +"essuie-plume", +"essuie-plumes", +"essuie-tout", +"essuie-touts", +"essuie-verre", +"essuie-verres", +"estrée-blanchois", +"estrée-blanchoise", +"estrée-blanchoises", +"estrée-cauchois", +"estrée-cauchoise", +"estrée-cauchoises", +"estrée-waminois", +"estrée-waminoise", +"estrée-waminoises", +"ethnico-religieux", +"euro-africain", +"euro-africaines", +"euro-asiatique", +"euro-asiatiques", +"euro-bashing", +"euro-manifestation", +"euro-manifestations", +"euro-obligation", +"euro-obligations", +"eusses-tu-cru", +"eux-mêmes", +"ex-Zaïre", +"ex-aequo", +"ex-ante", +"ex-champions", +"ex-copains", +"ex-député", +"ex-députée", +"ex-députées", +"ex-députés", +"ex-femme", +"ex-femmes", +"ex-fumeur", +"ex-fumeurs", +"ex-libris", +"ex-mari", +"ex-maris", +"ex-petits", +"ex-présidents", +"ex-sacs", +"ex-sergents", +"ex-serviteurs", +"ex-soldats", +"ex-strip-teaseuse", +"ex-voto", +"ex-votos", +"ex-æquo", +"exa-ampère", +"exa-ampères", +"exa-octet", +"exa-octets", +"exa-électron-volt", +"exa-électron-volts", +"exaélectron-volt", +"exaélectron-volts", +"excito-nervin", +"excito-nervine", +"excito-nervines", +"excito-nervins", +"excusez-moi", +"exo-noyau", +"exo-noyaux", +"expert-comptable", +"extracto-chargeur", +"extracto-chargeurs", +"extracto-résine", +"extracto-résineux", +"extro-déterminé", +"extrêmes-droites", +"extrêmes-gauches", +"extrêmes-onctions", +"eye-liner", +"eye-liners", +"f'jer", +"f'jers", +"f'nêtre", +"f'nêtres", +"fac-simila", +"fac-similai", +"fac-similaient", +"fac-similaire", +"fac-similais", +"fac-similait", +"fac-similant", +"fac-similas", +"fac-similasse", +"fac-similassent", +"fac-similasses", +"fac-similassiez", +"fac-similassions", +"fac-simile", +"fac-similent", +"fac-similer", +"fac-similera", +"fac-similerai", +"fac-simileraient", +"fac-similerais", +"fac-similerait", +"fac-simileras", +"fac-similerez", +"fac-simileriez", +"fac-similerions", +"fac-similerons", +"fac-simileront", +"fac-similes", +"fac-similez", +"fac-similiez", +"fac-similions", +"fac-similons", +"fac-similâmes", +"fac-similât", +"fac-similâtes", +"fac-similèrent", +"fac-similé", +"fac-similée", +"fac-similées", +"fac-similés", +"face-B", +"face-kini", +"face-kinis", +"face-sitting", +"face-sittings", +"face-à-face", +"face-à-main", +"faces-B", +"faces-à-main", +"faches-thumesnilois", +"faches-thumesniloise", +"faches-thumesniloises", +"faim-valle", +"fair-play", +"fair-plays", +"faire-part", +"faire-savoir", +"faire-valoir", +"fait-divers", +"fait-diversier", +"fait-diversiers", +"fait-main", +"fait-tout", +"fait-à-fait", +"faits-divers", +"faits-diversier", +"faits-diversiers", +"fan-club", +"fan-clubs", +"fancy-fair", +"fancy-fairs", +"farcy-pontain", +"farcy-pontaine", +"farcy-pontaines", +"farcy-pontains", +"fast-food", +"fast-foods", +"fausse-braie", +"fausse-couche", +"fausse-limande", +"fausse-monnayeuse", +"fausse-porte", +"fausses-braies", +"fausses-couches", +"fausses-monnayeuses", +"faux-acacia", +"faux-acacias", +"faux-ami", +"faux-amis", +"faux-bourdon", +"faux-bourdons", +"faux-bras", +"faux-carré", +"faux-carrés", +"faux-champlevé", +"faux-col", +"faux-cols", +"faux-cul", +"faux-derche", +"faux-derches", +"faux-filet", +"faux-filets", +"faux-frais", +"faux-fruit", +"faux-fruits", +"faux-frère", +"faux-frères", +"faux-fuyans", +"faux-fuyant", +"faux-fuyants", +"faux-garou", +"faux-grenier", +"faux-greniers", +"faux-jeton", +"faux-jetons", +"faux-monnayage", +"faux-monnayages", +"faux-monnayeur", +"faux-monnayeurs", +"faux-nez", +"faux-palais", +"faux-persil", +"faux-poivrier", +"faux-poivriers", +"faux-pont", +"faux-ponts", +"faux-positif", +"faux-positifs", +"faux-saunage", +"faux-saunier", +"faux-sauniers", +"faux-saunière", +"faux-saunières", +"faux-scaphirhynque", +"faux-semblans", +"faux-semblant", +"faux-semblants", +"faux-sens", +"faux-vampire", +"faux-vampires", +"faux-vin", +"fax-tractage", +"fax-tractages", +"fayl-billotin", +"fayl-billotine", +"fayl-billotines", +"fayl-billotins", +"fech-fech", +"feed-back", +"femelle-stérile", +"femelle-stériles", +"femme-enfant", +"femme-objet", +"femme-orchestre", +"femme-renarde", +"femmes-enfants", +"femmes-orchestres", +"femmes-renardes", +"femto-ohm", +"femto-ohms", +"fer-blanc", +"fer-chaud", +"fer-de-lance", +"fer-de-moulin", +"fer-à-cheval", +"ferme-bourse", +"ferme-circuit", +"ferme-circuits", +"ferme-porte", +"ferme-portes", +"fermes-hôtels", +"fermier-général", +"ferrando-forézienne", +"ferre-mule", +"ferro-axinite", +"ferro-axinites", +"ferro-magnésien", +"ferro-magnétisme", +"ferro-magnétismes", +"ferro-phlogopite", +"ferro-phlogopites", +"ferro-prussiate", +"ferro-prussiates", +"ferry-boat", +"ferry-boats", +"fers-blancs", +"fers-de-lance", +"fers-à-cheval", +"fesh-fesh", +"fesse-cahier", +"fesse-mathieu", +"fesse-mathieus", +"fesse-mathieux", +"fesse-tonneau", +"fesse-tonneaux", +"fest-deiz", +"fest-noz", +"fest-nozs", +"feuille-caillou-ciseaux", +"feuille-morte", +"fibre-cellule", +"fibro-cartilage", +"fibro-cellulaire", +"fibro-cystique", +"fibro-cystiques", +"fibro-granulaire", +"fibro-muqueux", +"fibro-soyeux", +"fibro-séreux", +"fiche-échalas", +"fiducie-sûreté", +"fie-vïnnamide", +"fie-vïnnamides", +"fier-à-bras", +"fiers-à-bras", +"fifty-fifty", +"figuier-mûrier", +"filet-poubelle", +"filets-poubelles", +"fille-mère", +"filles-mères", +"film-fleuve", +"films-annonces", +"fils-de-puterie", +"filtre-presse", +"filtres-presses", +"fin-or", +"fine-metal", +"finno-ougrien", +"finno-ougrienne", +"finno-ougriennes", +"finno-ougriens", +"first-fit", +"fisse-larron", +"fisses-larrons", +"fist-fucking", +"fist-fuckings", +"fitz-jamois", +"fitz-jamoise", +"fitz-jamoises", +"fix-up", +"fixe-chaussette", +"fixe-chaussettes", +"fixe-fruit", +"fixe-fruits", +"fixe-longe", +"fixe-moustaches", +"fixe-ruban", +"fixe-rubans", +"fla-fla", +"fla-flas", +"flanc-de-chien", +"flanc-garde", +"flanc-gardes", +"flanc-mou", +"flancs-de-chien", +"flancs-gardes", +"flancs-mous", +"flash-back", +"flash-ball", +"flash-balls", +"flash-mob", +"flash-mobs", +"fleur-bleuisa", +"fleur-bleuisai", +"fleur-bleuisaient", +"fleur-bleuisais", +"fleur-bleuisait", +"fleur-bleuisant", +"fleur-bleuisas", +"fleur-bleuisasse", +"fleur-bleuisassent", +"fleur-bleuisasses", +"fleur-bleuisassiez", +"fleur-bleuisassions", +"fleur-bleuise", +"fleur-bleuisent", +"fleur-bleuiser", +"fleur-bleuisera", +"fleur-bleuiserai", +"fleur-bleuiseraient", +"fleur-bleuiserais", +"fleur-bleuiserait", +"fleur-bleuiseras", +"fleur-bleuiserez", +"fleur-bleuiseriez", +"fleur-bleuiserions", +"fleur-bleuiserons", +"fleur-bleuiseront", +"fleur-bleuises", +"fleur-bleuisez", +"fleur-bleuisiez", +"fleur-bleuisions", +"fleur-bleuisons", +"fleur-bleuisâmes", +"fleur-bleuisât", +"fleur-bleuisâtes", +"fleur-bleuisèrent", +"fleur-bleuisé", +"fleur-bleuisée", +"fleur-bleuisées", +"fleur-bleuisés", +"fleur-de-mai", +"fleur-feuille", +"flic-flac", +"flic-flaqua", +"flic-flaquai", +"flic-flaquaient", +"flic-flaquais", +"flic-flaquait", +"flic-flaquant", +"flic-flaquas", +"flic-flaquasse", +"flic-flaquassent", +"flic-flaquasses", +"flic-flaquassiez", +"flic-flaquassions", +"flic-flaque", +"flic-flaquent", +"flic-flaquer", +"flic-flaquera", +"flic-flaquerai", +"flic-flaqueraient", +"flic-flaquerais", +"flic-flaquerait", +"flic-flaqueras", +"flic-flaquerez", +"flic-flaqueriez", +"flic-flaquerions", +"flic-flaquerons", +"flic-flaqueront", +"flic-flaques", +"flic-flaquez", +"flic-flaquiez", +"flic-flaquions", +"flic-flaquons", +"flic-flaquâmes", +"flic-flaquât", +"flic-flaquâtes", +"flic-flaquèrent", +"flic-flaqué", +"flint-glass", +"flip-flap", +"flirty-fishing", +"float-tube", +"float-tubes", +"flos-ferri", +"flos-ferré", +"flotte-tube", +"flotte-tubes", +"flou-flou", +"fluazifop-P-butyl", +"fluazifop-butyl", +"fluoro-phlogopite", +"fluoro-phlogopites", +"flupyrsulfuron-méthyle", +"fluroxypyr-meptyl", +"fluvio-marin", +"fly-over", +"fly-overs", +"fly-tox", +"foc-en-l'air", +"foi-menti", +"foi-mentie", +"foie-de-boeuf", +"foies-de-boeuf", +"foire-exposition", +"foires-expositions", +"folk-lore", +"folk-lores", +"folle-avoine", +"folle-blanche", +"folle-verte", +"folles-avoines", +"folx-les-cavien", +"fon-gbe", +"fond-de-teinta", +"fond-de-teintai", +"fond-de-teintaient", +"fond-de-teintais", +"fond-de-teintait", +"fond-de-teintant", +"fond-de-teintas", +"fond-de-teintasse", +"fond-de-teintassent", +"fond-de-teintasses", +"fond-de-teintassiez", +"fond-de-teintassions", +"fond-de-teinte", +"fond-de-teintent", +"fond-de-teinter", +"fond-de-teintera", +"fond-de-teinterai", +"fond-de-teinteraient", +"fond-de-teinterais", +"fond-de-teinterait", +"fond-de-teinteras", +"fond-de-teinterez", +"fond-de-teinteriez", +"fond-de-teinterions", +"fond-de-teinterons", +"fond-de-teinteront", +"fond-de-teintes", +"fond-de-teintez", +"fond-de-teintiez", +"fond-de-teintions", +"fond-de-teintons", +"fond-de-teintâmes", +"fond-de-teintât", +"fond-de-teintâtes", +"fond-de-teintèrent", +"fond-de-teinté", +"fond-de-teintée", +"fond-de-teintées", +"fond-de-teintés", +"fontaine-brayen", +"fontaine-brayenne", +"fontaine-brayennes", +"fontaine-brayens", +"food-court", +"food-courts", +"food-truck", +"food-trucks", +"force-vivier", +"forge-mètre", +"formica-leo", +"formule-choc", +"formule-chocs", +"forsétyl-al", +"forte-piano", +"forte-pianos", +"forts-vêtu", +"forêt-clairière", +"forêt-climax", +"forêt-galerie", +"forêt-parc", +"forêts-clairières", +"forêts-climax", +"forêts-galeries", +"forêts-parcs", +"fosétyl-Al", +"fouette-cul", +"fouette-culs", +"fouette-queue", +"fouette-queues", +"fougère-aigle", +"fougères-aigles", +"fouille-au-pot", +"fouille-merde", +"foule-crapaud", +"fourche-fière", +"fourmi-lion", +"fourmis-lions", +"fourre-tout", +"foué-toutrac", +"foué-toutracs", +"fox-hound", +"fox-hounds", +"fox-terrier", +"fox-terriers", +"fox-trot", +"fox-trott", +"fox-trotta", +"fox-trottai", +"fox-trottaient", +"fox-trottais", +"fox-trottait", +"fox-trottant", +"fox-trottas", +"fox-trottasse", +"fox-trottassent", +"fox-trottasses", +"fox-trottassiez", +"fox-trottassions", +"fox-trotte", +"fox-trottent", +"fox-trotter", +"fox-trottera", +"fox-trotterai", +"fox-trotteraient", +"fox-trotterais", +"fox-trotterait", +"fox-trotteras", +"fox-trotterez", +"fox-trotteriez", +"fox-trotterions", +"fox-trotterons", +"fox-trotteront", +"fox-trottes", +"fox-trottez", +"fox-trottiez", +"fox-trottions", +"fox-trottons", +"fox-trotts", +"fox-trottâmes", +"fox-trottât", +"fox-trottâtes", +"fox-trottèrent", +"fox-trotté", +"foy-notre-damien", +"frais-chier", +"frappe-abord", +"frappe-babord", +"frappe-d'abord", +"frappe-devant", +"frappe-main", +"frappe-mains", +"frappe-plaque", +"frappe-plaques", +"frappe-à-bord", +"frappe-à-mort", +"free-lance", +"frein-vapeur", +"freins-vapeur", +"freyming-merlebachois", +"freyming-merlebachoise", +"freyming-merlebachoises", +"fric-frac", +"fric-fracs", +"fronto-iniaque", +"frou-frou", +"frou-frous", +"frous-frous", +"fuel-oil", +"fuel-oils", +"full-contact", +"full-stack", +"fulmi-coton", +"fulmi-cotons", +"fume-cigare", +"fume-cigares", +"fume-cigarette", +"fume-cigarettes", +"fumée-gelée", +"fusil-mitrailleur", +"fusilier-commando", +"fusilier-marin", +"fusiliers-commandos", +"fusiliers-marins", +"fusils-mitrailleurs", +"fusion-acquisition", +"fusée-sonde", +"fut's", +"fute-fute", +"futes-futes", +"futuna-aniwa", +"fémoro-tibial", +"fénoxaprop-P-éthyl", +"fénoxaprop-éthyl", +"féodo-vassalique", +"féodo-vassaliques", +"fétu-en-cul", +"fétus-en-cul", +"fût-et-fare", +"g-strophanthine", +"gabrielino-fernandeño", +"gadz'arts", +"gagnant-gagnant", +"gagnant-gagnant-gagnant", +"gagnante-gagnante", +"gagnante-gagnante-gagnante", +"gagnantes-gagnantes", +"gagnantes-gagnantes-gagnantes", +"gagnants-gagnants", +"gagnants-gagnants-gagnants", +"gagne-pain", +"gagne-pains", +"gagne-petit", +"gaillet-gratteron", +"gaillets-gratterons", +"gaine-culotte", +"gaines-culottes", +"galaïco-portugais", +"galeries-refuges", +"galette-saucisse", +"galette-saucisses", +"galvano-cautère", +"galvano-magnétique", +"galvano-magnétiques", +"galvano-magnétisme", +"galvano-magnétismes", +"galégo-portugais", +"gamma-1,2,3,4,5,6-hexachlorocyclohexane", +"gamma-HCH", +"gamma-hexachlorobenzène", +"gamma-hexachlorocyclohexane", +"garcette-goitre", +"garden-parties", +"garden-party", +"garden-partys", +"gas-oil", +"gas-oils", +"gauche-fer", +"gay-friendly", +"gays-friendly", +"gaz-cab", +"gaz-poivre", +"gazelle-girafe", +"gel-douche", +"gel-douches", +"gentleman-rider", +"gentlemen-riders", +"germanate-analcime", +"germanate-analcimes", +"germano-américain", +"germano-américaine", +"germano-américaines", +"germano-américains", +"germano-anglais", +"germano-anglaises", +"germano-iranien", +"germano-italo-japonais", +"germo-roburien", +"germo-roburienne", +"germo-roburiennes", +"germo-roburiens", +"gestalt-thérapie", +"gestalt-thérapies", +"giga-ampère", +"giga-ampères", +"giga-ohm", +"giga-ohms", +"giga-électron-volt", +"giga-électron-volts", +"gigabit-ethernet", +"gigaélectron-volt", +"gigaélectron-volts", +"gill-box", +"glabello-iniaque", +"glass-cord", +"glauco-ferrugineuse", +"glauco-ferrugineuses", +"glauco-ferrugineux", +"glisser-déposer", +"globe-trotter", +"globe-trotters", +"globe-trotteur", +"globe-trotteurs", +"globe-trotteuse", +"globe-trotteuses", +"glosso-pharyngien", +"glosso-staphylin", +"glosso-staphylins", +"glosso-épiglottique", +"glosso-épiglottiques", +"gloubi-boulga", +"gluco-corticoïde", +"gluco-corticoïdes", +"glufosinate-ammonium", +"glycosyl-phosphatidylinositol", +"glycéraldéhyde-3-phosphate", +"go-slow", +"goal-average", +"goal-averages", +"goal-ball", +"gobe-dieu", +"gobe-goujons", +"gobe-mouche", +"gobe-moucherie", +"gobe-moucherons", +"gobe-mouches", +"gobe-mouton", +"gode-ceinture", +"gode-miché", +"gode-michés", +"godes-ceintures", +"goma-dare", +"gomme-cogne", +"gomme-cognes", +"gomme-gutte", +"gomme-résine", +"gommo-résineux", +"google-isa", +"google-isai", +"google-isaient", +"google-isais", +"google-isait", +"google-isant", +"google-isas", +"google-isasse", +"google-isassent", +"google-isasses", +"google-isassiez", +"google-isassions", +"google-ise", +"google-isent", +"google-iser", +"google-isera", +"google-iserai", +"google-iseraient", +"google-iserais", +"google-iserait", +"google-iseras", +"google-iserez", +"google-iseriez", +"google-iserions", +"google-iserons", +"google-iseront", +"google-ises", +"google-isez", +"google-isiez", +"google-isions", +"google-isons", +"google-isâmes", +"google-isât", +"google-isâtes", +"google-isèrent", +"google-isé", +"google-isée", +"google-isées", +"google-isés", +"gorge-bleue", +"gorge-de-pigeon", +"gorge-fouille", +"gourdan-polignanais", +"gourdan-polignanaise", +"gourdan-polignanaises", +"gouris-taitien", +"gouris-taitienne", +"gouris-taitiennes", +"gouris-taitiens", +"goutte-de-sang", +"goutte-de-suif", +"goutte-rose", +"goutte-à-goutte", +"gouttes-de-sang", +"gouzi-gouzi", +"gouzis-gouzis", +"goyave-ananas", +"goyaves-ananas", +"gracieux-berluron", +"grain-d'orge", +"grand'chose", +"grand'faim", +"grand'garde", +"grand'gardes", +"grand'hamien", +"grand'hamienne", +"grand'hamiennes", +"grand'hamiens", +"grand'honte", +"grand'hontes", +"grand'landais", +"grand'landaise", +"grand'landaises", +"grand'maman", +"grand'mamans", +"grand'maternité", +"grand'maternités", +"grand'messe", +"grand'messes", +"grand'mère", +"grand'mères", +"grand'paternité", +"grand'paternités", +"grand'tante", +"grand'tantes", +"grandgousier-pélican", +"grano-lamellaire", +"grap-fruit", +"grap-fruits", +"grapho-moteur", +"grappe-fruit", +"gras-double", +"gras-doubles", +"gras-fondu", +"grattes-ciels", +"grave-cimens", +"grave-ciment", +"grave-ciments", +"graves-ciment", +"gravi-kora", +"gray-la-villois", +"gray-la-villoise", +"gray-la-villoises", +"grenadier-voltigeur", +"grenadiers-voltigeurs", +"grenouille-taureau", +"grenouilles-taureaux", +"grez-neuvillois", +"grez-neuvilloise", +"grez-neuvilloises", +"gri-gri", +"gri-gris", +"griche-dents", +"gril-au-vent", +"grille-midi", +"grille-pain", +"grille-pains", +"grippe-argent", +"grippe-chair", +"grippe-fromage", +"grippe-fromages", +"grippe-minaud", +"grippe-minauds", +"grippe-sou", +"grippe-sous", +"gris-farinier", +"gris-fariniers", +"gris-gris", +"gris-pendart", +"gris-pendarts", +"grise-bonne", +"grises-bonnes", +"grosse-de-fonte", +"grosse-gorge", +"grosso-modo", +"grâcieux-hollognois", +"guarasu'we", +"guerre-éclair", +"guet-apens", +"guet-appens", +"guet-à-pent", +"guets-apens", +"guette-chemin", +"gueule-bée", +"gueule-de-loup", +"gueules-de-loup", +"guide-fil", +"guide-fils", +"guide-main", +"guide-âne", +"guide-ânes", +"guigne-cul", +"guigne-culs", +"guilherandais-grangeois", +"guilherandaise-grangeoise", +"guilherandaises-grangeoises", +"guili-guili", +"guili-guilis", +"guillemet-apostrophe", +"guillemets-apostrophes", +"guit-guit", +"guitare-harpe", +"guitare-violoncelle", +"guitare-violoncelles", +"gulf-stream", +"gulf-streams", +"gusathion-méthyl", +"gusathion-éthyl", +"gut-komm", +"gutta-percha", +"gutturo-maxillaire", +"gué-d'allérien", +"gué-d'allérienne", +"gué-d'allériennes", +"gué-d'allériens", +"gwich'in", +"gâche-métier", +"gâte-bois", +"gâte-ménage", +"gâte-ménages", +"gâte-métier", +"gâte-métiers", +"gâte-papier", +"gâte-papiers", +"gâte-pâte", +"gâte-sauce", +"gâte-sauces", +"gère-bélestinois", +"gère-bélestinoise", +"gère-bélestinoises", +"gélatino-bromure", +"gélatino-bromures", +"génie-conseil", +"génies-conseils", +"génio-hyoïdien", +"génio-hyoïdienne", +"génio-hyoïdiennes", +"génio-hyoïdiens", +"génito-crural", +"génito-urinaire", +"génito-urinaires", +"gétah-lahoë", +"ha-ha", +"ha-has", +"hache-bâché", +"hache-légume", +"hache-légumes", +"hache-paille", +"hache-pailles", +"hache-écorce", +"hache-écorces", +"hagio-onomastique", +"hagio-onomastiques", +"hakko-ryu", +"hale-avans", +"hale-avant", +"hale-avants", +"hale-bas", +"hale-breu", +"hale-croc", +"hale-dedans", +"hale-dehors", +"hale-à-bord", +"haleine-de-Jupiter", +"haleines-de-Jupiter", +"half-and-half", +"half-pipe", +"half-pipes", +"half-track", +"half-tracks", +"halo-halo", +"halo-lunaire", +"halos-lunaires", +"haloxyfop-R", +"haloxyfop-éthoxyéthyl", +"halte-garderie", +"halte-garderies", +"halte-là", +"haltes-garderies", +"halvadji-bachi", +"ham-nalinnois", +"hames-boucrois", +"hames-boucroise", +"hames-boucroises", +"hamme-millois", +"handi-accessible", +"handi-accessibles", +"happe-chair", +"happe-chat", +"happe-foie", +"hara-kiri", +"hara-kiris", +"hara-kiriser", +"harai-goshi", +"haraï-goshi", +"hard-discount", +"hard-discountisa", +"hard-discountisai", +"hard-discountisaient", +"hard-discountisais", +"hard-discountisait", +"hard-discountisant", +"hard-discountisas", +"hard-discountisasse", +"hard-discountisassent", +"hard-discountisasses", +"hard-discountisassiez", +"hard-discountisassions", +"hard-discountise", +"hard-discountisent", +"hard-discountiser", +"hard-discountisera", +"hard-discountiserai", +"hard-discountiseraient", +"hard-discountiserais", +"hard-discountiserait", +"hard-discountiseras", +"hard-discountiserez", +"hard-discountiseriez", +"hard-discountiserions", +"hard-discountiserons", +"hard-discountiseront", +"hard-discountises", +"hard-discountisez", +"hard-discountisiez", +"hard-discountisions", +"hard-discountisons", +"hard-discountisâmes", +"hard-discountisât", +"hard-discountisâtes", +"hard-discountisèrent", +"hard-discountisé", +"hard-discountisée", +"hard-discountisées", +"hard-discountisés", +"hard-discounts", +"hardi-petit", +"harpe-guitare", +"harpe-luth", +"has-been", +"has-beens", +"hausse-col", +"hausse-cols", +"hausse-pied", +"hausse-pieds", +"hausse-queue", +"haye-le-comtois", +"haye-le-comtoise", +"haye-le-comtoises", +"hecto-ohm", +"hecto-ohms", +"hentai-gana", +"herbe-au-bitume", +"herbe-aux-femmes-battues", +"herbe-aux-plaies", +"herbe-à-cochon", +"herbes-au-bitume", +"herbes-aux-femmes-battues", +"herbes-aux-plaies", +"herbes-aux-taupes", +"herbes-à-cochon", +"herd-book", +"heure-homme", +"heure-lumière", +"heures-hommes", +"heures-lumière", +"heurte-pot", +"hexa-core", +"hexa-cores", +"hexa-rotor", +"hexa-rotors", +"hi-fi", +"hi-han", +"high-life", +"high-tech", +"himène-plume", +"hip-hop", +"hip-hopisa", +"hip-hopisai", +"hip-hopisaient", +"hip-hopisais", +"hip-hopisait", +"hip-hopisant", +"hip-hopisas", +"hip-hopisasse", +"hip-hopisassent", +"hip-hopisasses", +"hip-hopisassiez", +"hip-hopisassions", +"hip-hopise", +"hip-hopisent", +"hip-hopiser", +"hip-hopisera", +"hip-hopiserai", +"hip-hopiseraient", +"hip-hopiserais", +"hip-hopiserait", +"hip-hopiseras", +"hip-hopiserez", +"hip-hopiseriez", +"hip-hopiserions", +"hip-hopiserons", +"hip-hopiseront", +"hip-hopises", +"hip-hopisez", +"hip-hopisiez", +"hip-hopisions", +"hip-hopisons", +"hip-hopisâmes", +"hip-hopisât", +"hip-hopisâtes", +"hip-hopisèrent", +"hip-hopisé", +"hip-hopisée", +"hip-hopisées", +"hip-hopisés", +"hippocampe-feuillu", +"hippocampes-feuillus", +"hispano-américain", +"hispano-américaine", +"hispano-américaines", +"hispano-américains", +"hispano-arabe", +"hispano-arabes", +"hispano-mauresque", +"hispano-moresque", +"hispano-moresques", +"histoire-géo", +"historico-culturelle", +"hit-parade", +"hit-parades", +"hitléro-trotskisme", +"hitléro-trotskiste", +"hoat-chi", +"hoche-cul", +"hoche-culs", +"hoche-queue", +"hokkaïdo-ken", +"hold-up", +"home-jacking", +"home-jackings", +"home-sitter", +"home-sitters", +"home-sitting", +"home-sittings", +"home-trainer", +"home-trainers", +"homme-animal", +"homme-chacal", +"homme-clé", +"homme-femme", +"homme-fourmi", +"homme-grenouille", +"homme-loup", +"homme-léopard", +"homme-mort", +"homme-morts", +"homme-objet", +"homme-orchestre", +"homme-robot", +"homme-sandwich", +"homme-tronc", +"hommes-chacals", +"hommes-clés", +"hommes-femmes", +"hommes-fourmis", +"hommes-grenouilles", +"hommes-loups", +"hommes-léopards", +"hommes-objets", +"hommes-orchestres", +"hommes-robots", +"hommes-sandwiches", +"hommes-sandwichs", +"hommes-troncs", +"homo-épitaxie", +"homo-épitaxies", +"hon-hergeois", +"hon-hergeoise", +"hon-hergeoises", +"honey-dew", +"hong-kongais", +"hong-kongaise", +"hong-kongaises", +"horo-kilométrique", +"horo-kilométriques", +"hors-bord", +"hors-bords", +"hors-champ", +"hors-concours", +"hors-d'oeuvre", +"hors-d'œuvre", +"hors-fonds", +"hors-jeu", +"hors-jeux", +"hors-la-loi", +"hors-ligne", +"hors-lignes", +"hors-norme", +"hors-piste", +"hors-pistes", +"hors-sac", +"hors-service", +"hors-sol", +"hors-sols", +"hors-sujet", +"hors-série", +"hors-séries", +"hors-temps", +"hors-texte", +"hors-textes", +"horse-ball", +"horse-guard", +"horse-guards", +"hostello-flavien", +"hostello-flavienne", +"hostello-flaviennes", +"hostello-flaviens", +"hot-dog", +"hot-dogs", +"hot-melt", +"hot-melts", +"hot-plug", +"houl'eau", +"house-boats", +"houx-frelon", +"houx-frelons", +"huis-clos", +"huit-marsiste", +"huit-marsistes", +"huit-pieds", +"huit-reflets", +"huit-ressorts", +"huitante-neuf", +"huitante-neuvième", +"huitante-neuvièmes", +"hume-vent", +"huppe-col", +"huron-wendat", +"hydrargyro-cyanate", +"hydrargyro-cyanates", +"hydraulico-pneumatique", +"hydro-aviation", +"hydro-aviations", +"hydro-avion", +"hydro-avions", +"hydro-ensemencement", +"hydro-ensemencements", +"hydro-météorologie", +"hydro-électricité", +"hydro-électricités", +"hydro-électrique", +"hydro-électriques", +"hyo-pharyngien", +"hyo-épiglottique", +"hyo-épiglottiques", +"hypo-centre", +"hypo-centres", +"hypo-iodeuse", +"hypo-iodeuses", +"hypo-iodeux", +"hypothético-déductif", +"hystéro-catalepsie", +"hystéro-catalepsies", +"hystéro-épilepsie", +"hystéro-épilepsies", +"hyène-garou", +"hyènes-garous", +"hâ-hâ", +"hâ-hâs", +"hémi-dodécaèdre", +"hémi-octaèdre", +"hémi-épiphyte", +"hémi-épiphytes", +"hépato-biliaire", +"hépato-cystique", +"hépato-cystiques", +"hépato-gastrique", +"hépato-gastrite", +"hépato-gastrites", +"héroï-comique", +"héroï-comiques", +"hétéro-céphalophorie", +"hétéro-céphalophories", +"hétéro-réparation", +"hétéro-réparations", +"hétéro-épitaxie", +"hétéro-évaluation", +"hétéro-évaluations", +"hôtel-Dieu", +"hôtellerie-restauration", +"hôtels-Dieu", +"i-butane", +"i-butanes", +"i.-e.", +"iatro-magique", +"iatro-magiques", +"ibéro-roman", +"ice-belt", +"ice-belts", +"ice-berg", +"ice-bergs", +"ice-blink", +"ice-blinks", +"ice-bloc", +"ice-blocs", +"ice-cream", +"ice-creams", +"ice-foot", +"ice-foots", +"ice-rapt", +"ice-rapts", +"ice-table", +"ice-tables", +"ici-bas", +"idio-électricité", +"idio-électrique", +"idio-électriques", +"idéal-type", +"idée-force", +"idée-maîtresse", +"idées-forces", +"idées-maîtresses", +"ifira-mele", +"ifira-meles", +"igny-marin", +"igny-marine", +"igny-marines", +"igny-marins", +"iliaco-fémoral", +"iliaco-musculaire", +"ilio-pectiné", +"ilio-pubien", +"ilio-scrotal", +"ilo-dionysien", +"ilo-dionysienne", +"ilo-dionysiennes", +"ilo-dionysiens", +"iléo-colique", +"iléo-coliques", +"iléo-cæcal", +"iléo-cæcale", +"iléo-cæcales", +"iléo-cæcaux", +"iléos-meldois", +"iléos-meldoise", +"iléos-meldoises", +"image-gradient", +"imazaméthabenz-méthyl", +"immuno-pharmacologie", +"immuno-pharmacologies", +"impari-nervié", +"impari-nervé", +"impari-penné", +"import-export", +"impératrice-mère", +"impératrices-mères", +"in-12", +"in-12º", +"in-16", +"in-16º", +"in-18", +"in-18º", +"in-32", +"in-4", +"in-4.º", +"in-4to", +"in-4º", +"in-6", +"in-6º", +"in-8", +"in-8.º", +"in-8vo", +"in-8º", +"in-cent-vingt-huit", +"in-dix-huit", +"in-douze", +"in-duodecimo", +"in-folio", +"in-fº", +"in-huit", +"in-manus", +"in-octavo", +"in-plano", +"in-plº", +"in-promptu", +"in-quarto", +"in-sedecimo", +"in-seize", +"in-six", +"in-trente-deux", +"in-vingt-quatre", +"in-vitro", +"inch'Allah", +"inch'allah", +"incito-moteur", +"incito-motricité", +"income-tax", +"indane-1,3-dione", +"inde-plate", +"india-océanisme", +"india-océanismes", +"info-ballon", +"info-ballons", +"info-bulle", +"info-bulles", +"ingénieur-conseil", +"ingénieur-docteur", +"ingénieur-maître", +"ingénieure-conseil", +"ingénieures-conseils", +"ingénieurs-conseils", +"ingénieurs-docteurs", +"ingénieurs-maîtres", +"injonction-bâillon", +"insecto-mortifère", +"insecto-mortifères", +"inspecteur-chef", +"inspecteurs-chefs", +"insulino-dépendant", +"insulino-dépendante", +"insulino-dépendantes", +"insulino-dépendants", +"interno-médial", +"interro-négatif", +"intervertébro-costal", +"inuit-aléoute", +"inuit-aléoutes", +"iodo-borique", +"iodo-chlorure", +"iodosulfuron-méthyl-sodium", +"iowa-oto", +"iowa-otos", +"ischio-anal", +"ischio-clitorien", +"ischio-fémoral", +"ischio-fémorale", +"ischio-fémorales", +"ischio-fémoraux", +"ischio-jambier", +"ischio-jambiers", +"ischio-jambière", +"ischio-jambières", +"ischio-périnéal", +"ischio-tibial", +"ischio-tibiaux", +"isoxadifen-éthyl", +"israélo-syrienne", +"istro-roumain", +"ivre-mort", +"ivre-morte", +"ivres-mortes", +"ivres-morts", +"j't'aime", +"jack-russell", +"jaguar-garou", +"jaguars-garous", +"jam-sessions", +"jambon-beurre", +"jambon-des-jardiniers", +"jambons-des-jardiniers", +"jaunay-clanais", +"jaunay-clanaise", +"jaunay-clanaises", +"jaï-alaï", +"jaï-alaïs", +"je-m'en-fichisme", +"je-m'en-fichismes", +"je-m'en-fichiste", +"je-m'en-fichistes", +"je-m'en-foutisme", +"je-m'en-foutismes", +"je-m'en-foutiste", +"je-m'en-foutistes", +"je-ne-sais-quoi", +"jeans-de-gand", +"jeans-de-janten", +"jet-set", +"jet-sets", +"jet-settisa", +"jet-settisai", +"jet-settisaient", +"jet-settisais", +"jet-settisait", +"jet-settisant", +"jet-settisas", +"jet-settisasse", +"jet-settisassent", +"jet-settisasses", +"jet-settisassiez", +"jet-settisassions", +"jet-settise", +"jet-settisent", +"jet-settiser", +"jet-settisera", +"jet-settiserai", +"jet-settiseraient", +"jet-settiserais", +"jet-settiserait", +"jet-settiseras", +"jet-settiserez", +"jet-settiseriez", +"jet-settiserions", +"jet-settiserons", +"jet-settiseront", +"jet-settises", +"jet-settisez", +"jet-settisiez", +"jet-settisions", +"jet-settisons", +"jet-settisâmes", +"jet-settisât", +"jet-settisâtes", +"jet-settisèrent", +"jet-settisé", +"jet-settisée", +"jet-settisées", +"jet-settisés", +"jet-stream", +"jet-streams", +"jette-bouts", +"jeu-malochois", +"jeu-malochoise", +"jeu-malochoises", +"jeu-parti", +"jiu-jitsu", +"joint-venture", +"joint-ventures", +"joli-bois", +"jour-homme", +"jour-lumière", +"jours-hommes", +"jours-lumière", +"ju-jitsu", +"ju-ju", +"judéo-allemand", +"judéo-alsacien", +"judéo-arabe", +"judéo-arabes", +"judéo-asiatique", +"judéo-bolchévisme", +"judéo-centrisme", +"judéo-christianisme", +"judéo-christiano-islamique", +"judéo-christiano-islamiques", +"judéo-christiano-musulman", +"judéo-chrétien", +"judéo-chrétienne", +"judéo-chrétiennes", +"judéo-chrétiens", +"judéo-espagnol", +"judéo-espagnole", +"judéo-espagnoles", +"judéo-espagnols", +"judéo-iranien", +"judéo-libyen", +"judéo-lybien", +"judéo-maçonnique", +"judéo-maçonniques", +"judéo-musulman", +"judéo-musulmans", +"judéo-nazi", +"judéo-nazis", +"juke-box", +"juke-boxes", +"jully-sarçois", +"jully-sarçoise", +"jully-sarçoises", +"junk-food", +"junk-foods", +"jupe-culotte", +"jupes-culottes", +"juridico-politique", +"juridico-politiques", +"jusque-là", +"juste-au-corps", +"juste-à-temps", +"juxta-position", +"juxta-positions", +"juǀ'hoan", +"jérôme-boschisme", +"jérôme-boschismes", +"k-voisinage", +"k-voisinages", +"k-way", +"k-ways", +"kali'na", +"kan-kan", +"kan-kans", +"kansai-ben", +"kara-gueuz", +"kara-kalpak", +"karachay-balkar", +"karafuto-ken", +"karatchaï-balkar", +"kem's", +"khambo-lama", +"khambo-lamas", +"khatti-chérif", +"khatti-chérifs", +"khi-carré", +"khi-carrés", +"khi-deux", +"kif-kif", +"kilo-ohm", +"kilo-ohms", +"kilo-électron-volt", +"kilo-électron-volts", +"kilo-électrons-volts", +"kilogramme-force", +"kilogramme-poids", +"kilogrammes-force", +"kilogrammes-poids", +"kilomètre-heure", +"kilomètres-heure", +"kiloélectron-volt", +"kiloélectron-volts", +"kiloélectrons-volts", +"kin-ball", +"kino-congolais", +"kip-kap", +"kip-kaps", +"kirsch-wasser", +"kirsch-wassers", +"kiss-in", +"kite-surf", +"kite-surfa", +"kite-surfai", +"kite-surfaient", +"kite-surfais", +"kite-surfait", +"kite-surfant", +"kite-surfas", +"kite-surfasse", +"kite-surfassent", +"kite-surfasses", +"kite-surfassiez", +"kite-surfassions", +"kite-surfe", +"kite-surfent", +"kite-surfer", +"kite-surfera", +"kite-surferai", +"kite-surferaient", +"kite-surferais", +"kite-surferait", +"kite-surferas", +"kite-surferez", +"kite-surferiez", +"kite-surferions", +"kite-surferons", +"kite-surferont", +"kite-surfers", +"kite-surfes", +"kite-surfez", +"kite-surfiez", +"kite-surfions", +"kite-surfons", +"kite-surfâmes", +"kite-surfât", +"kite-surfâtes", +"kite-surfèrent", +"kite-surfé", +"knicker-bocker", +"knicker-bockers", +"knock-out", +"knock-outa", +"knock-outai", +"knock-outaient", +"knock-outais", +"knock-outait", +"knock-outant", +"knock-outas", +"knock-outasse", +"knock-outassent", +"knock-outasses", +"knock-outassiez", +"knock-outassions", +"knock-oute", +"knock-outent", +"knock-outer", +"knock-outera", +"knock-outerai", +"knock-outeraient", +"knock-outerais", +"knock-outerait", +"knock-outeras", +"knock-outerez", +"knock-outeriez", +"knock-outerions", +"knock-outerons", +"knock-outeront", +"knock-outes", +"knock-outez", +"knock-outiez", +"knock-outions", +"knock-outons", +"knock-outs", +"knock-outâmes", +"knock-outât", +"knock-outâtes", +"knock-outèrent", +"knock-outé", +"knock-outée", +"knock-outées", +"knock-outés", +"ko-soto-gake", +"kouan-hoa", +"kouign-aman", +"kouign-amann", +"kouign-amanns", +"kouign-amans", +"krav-naga", +"krésoxim-méthyl", +"kung-fu", +"kwan-li-so", +"kérato-pharyngien", +"kérato-staphylin", +"kérato-staphylins", +"l-amphétamine", +"la-fertois", +"la-fertoise", +"la-fertoises", +"la-la-la", +"lab-ferment", +"lab-ferments", +"lac-laque", +"lac-laques", +"lac-à-l'épaule", +"lache-bras", +"lacrima-Christi", +"lacrima-christi", +"lacryma-Christi", +"lacryma-christi", +"lacs-à-l'épaule", +"lacto-végétarisme", +"lacto-végétarismes", +"laemmer-geier", +"laemmer-geiers", +"laisser-aller", +"laisser-allers", +"laisser-courre", +"laisser-faire", +"laisser-sur-place", +"laissez-faire", +"laissez-passer", +"laissé-pour-compte", +"laissée-pour-compte", +"laissées-pour-compte", +"laissés-pour-compte", +"lambda-cyhalothrine", +"lampe-tempête", +"lampes-tempête", +"lampris-lune", +"lance-amarres", +"lance-balles", +"lance-bombe", +"lance-bombes", +"lance-flamme", +"lance-flammes", +"lance-fusée", +"lance-fusées", +"lance-grenade", +"lance-grenades", +"lance-missile", +"lance-missiles", +"lance-patates", +"lance-pierre", +"lance-pierres", +"lance-roquette", +"lance-roquettes", +"lance-torpille", +"lance-torpilles", +"land-ice", +"land-ices", +"langue-de-boeuf", +"langue-de-chat", +"langue-de-moineau", +"langue-de-serpent", +"langue-de-vache", +"langue-toit", +"langues-de-boeuf", +"langues-de-chat", +"langues-de-vache", +"langues-toit", +"lanne-soubiranais", +"lanne-soubiranaise", +"lanne-soubiranaises", +"lapin-garou", +"lapins-garous", +"lapis-lazuli", +"lapu-lapu", +"larme-de-Job", +"larmes-de-Job", +"lau-balutin", +"lau-balutine", +"lau-balutines", +"lau-balutins", +"launay-villersois", +"launay-villersoise", +"launay-villersoises", +"laurier-cerise", +"laurier-rose", +"laurier-sauce", +"laurier-tarte", +"laurier-thym", +"laurier-tin", +"lauriers-cerises", +"lauriers-roses", +"lauriers-tins", +"laval-de-cérois", +"laval-de-céroise", +"laval-de-céroises", +"lavans-quingeois", +"lavans-quingeoise", +"lavans-quingeoises", +"lave-auto", +"lave-autos", +"lave-glace", +"lave-linge", +"lave-linges", +"lave-main", +"lave-mains", +"lave-pont", +"lave-ponts", +"lave-tête", +"lave-têtes", +"lave-vaisselle", +"lave-vaisselles", +"laveuse-sécheuse", +"lavé-de-vert", +"lavés-de-vert", +"lazur-apatite", +"lazur-apatites", +"lease-back", +"leather-jacket", +"lecteur-graveur", +"lecteurs-graveurs", +"lemmer-geyer", +"lemmer-geyers", +"lepto-kurticité", +"lepto-kurticités", +"lepto-kurtique", +"lepto-kurtiques", +"lever-dieu", +"lgbti-friendly", +"lgbti-phobie", +"lgbti-phobies", +"liane-corail", +"lianes-corail", +"liberum-veto", +"libidino-calotin", +"libre-choix", +"libre-penseur", +"libre-penseuse", +"libre-service", +"libre-échange", +"libre-échangisme", +"libre-échangismes", +"libre-échangiste", +"libre-échangistes", +"libres-choix", +"libres-penseurs", +"libres-penseuses", +"libres-services", +"libyco-berbère", +"libyco-berbères", +"libéral-conservateur", +"libéral-conservatisme", +"lice-po", +"liche-casse", +"licol-drisse", +"licols-drisses", +"lie-de-vin", +"lieu-dit", +"lieu-saint-amandinois", +"lieu-saint-amandinoise", +"lieu-saint-amandinoises", +"lieutenant-colonel", +"lieutenant-gouverneur", +"lieutenant-général", +"lieutenants-colonels", +"lieux-dits", +"ligne-de-foulée", +"lignes-de-foulée", +"limande-sole", +"limande-soles", +"limandes-soles", +"lime-bois", +"lime-uranite", +"lime-uranites", +"linon-batiste", +"linon-batistes", +"lion-garou", +"lions-garous", +"lire-écrire", +"lit-cage", +"lit-clos", +"litho-typographia", +"litho-typographiai", +"litho-typographiaient", +"litho-typographiais", +"litho-typographiait", +"litho-typographiant", +"litho-typographias", +"litho-typographiasse", +"litho-typographiassent", +"litho-typographiasses", +"litho-typographiassiez", +"litho-typographiassions", +"litho-typographie", +"litho-typographient", +"litho-typographier", +"litho-typographiera", +"litho-typographierai", +"litho-typographieraient", +"litho-typographierais", +"litho-typographierait", +"litho-typographieras", +"litho-typographierez", +"litho-typographieriez", +"litho-typographierions", +"litho-typographierons", +"litho-typographieront", +"litho-typographies", +"litho-typographiez", +"litho-typographiiez", +"litho-typographiions", +"litho-typographions", +"litho-typographiâmes", +"litho-typographiât", +"litho-typographiâtes", +"litho-typographièrent", +"litho-typographié", +"litho-typographiée", +"litho-typographiées", +"litho-typographiés", +"lits-cages", +"lits-clos", +"little-endian", +"living-room", +"living-rooms", +"livres-cassettes", +"livret-police", +"localité-type", +"location-financement", +"lock-out", +"lock-outa", +"lock-outai", +"lock-outaient", +"lock-outais", +"lock-outait", +"lock-outant", +"lock-outas", +"lock-outasse", +"lock-outassent", +"lock-outasses", +"lock-outassiez", +"lock-outassions", +"lock-oute", +"lock-outent", +"lock-outer", +"lock-outera", +"lock-outerai", +"lock-outeraient", +"lock-outerais", +"lock-outerait", +"lock-outeras", +"lock-outerez", +"lock-outeriez", +"lock-outerions", +"lock-outerons", +"lock-outeront", +"lock-outes", +"lock-outez", +"lock-outiez", +"lock-outions", +"lock-outons", +"lock-outs", +"lock-outâmes", +"lock-outât", +"lock-outâtes", +"lock-outèrent", +"lock-outé", +"lock-outée", +"lock-outées", +"lock-outés", +"locoalo-mendonnais", +"locoalo-mendonnaise", +"locoalo-mendonnaises", +"locution-phrase", +"locutions-phrases", +"loemmer-geyer", +"loemmer-geyers", +"logan-berry", +"logan-berrys", +"logiciel-socle", +"logo-syllabique", +"logo-syllabiques", +"loi-cadre", +"loi-programme", +"loi-écran", +"lois-cadre", +"lois-programme", +"lois-écrans", +"lombo-costal", +"lombo-costo-trachélien", +"lombo-dorso-trachélien", +"lombo-huméral", +"lombo-sacré", +"lombri-composta", +"lombri-compostai", +"lombri-compostaient", +"lombri-compostais", +"lombri-compostait", +"lombri-compostant", +"lombri-compostas", +"lombri-compostasse", +"lombri-compostassent", +"lombri-compostasses", +"lombri-compostassiez", +"lombri-compostassions", +"lombri-composte", +"lombri-compostent", +"lombri-composter", +"lombri-compostera", +"lombri-composterai", +"lombri-composteraient", +"lombri-composterais", +"lombri-composterait", +"lombri-composteras", +"lombri-composterez", +"lombri-composteriez", +"lombri-composterions", +"lombri-composterons", +"lombri-composteront", +"lombri-compostes", +"lombri-compostez", +"lombri-compostiez", +"lombri-compostions", +"lombri-compostons", +"lombri-compostâmes", +"lombri-compostât", +"lombri-compostâtes", +"lombri-compostèrent", +"lombri-composté", +"lombri-compostée", +"lombri-compostées", +"lombri-compostés", +"lompénie-serpent", +"long-courrier", +"long-courriers", +"long-grain", +"long-jointé", +"long-jointée", +"long-métrage", +"long-temps", +"long-tems", +"longs-courriers", +"longs-métrages", +"longue-langue", +"longue-vue", +"longue-épine", +"longues-langues", +"longues-vues", +"longues-épines", +"loqu'du", +"loqu'due", +"loqu'dues", +"loqu'dus", +"lord-lieutenance", +"lord-lieutenances", +"lord-lieutenant", +"lord-lieutenants", +"lord-maire", +"louise-bonne", +"louises-bonnes", +"loup-cerve", +"loup-cervier", +"loup-garou", +"loups-cerves", +"loups-cerviers", +"loups-garous", +"lourd-léger", +"lourds-légers", +"lourouzien-bourbonnais", +"lourouzienne-bourbonnaise", +"lourouziennes-bourbonnaises", +"lourouziens-bourbonnais", +"louve-garelle", +"louve-garolle", +"louve-garou", +"louves-garelles", +"louves-garolles", +"louves-garous", +"louveteau-garou", +"louveteaux-garous", +"louvie-soubironnais", +"louvie-soubironnaise", +"louvie-soubironnaises", +"love-in", +"low-cost", +"low-costs", +"low-tech", +"ludo-sportif", +"ludo-sportifs", +"ludo-sportive", +"ludo-sportives", +"ludo-éducatif", +"lui-même", +"lumen-seconde", +"lumens-secondes", +"luni-solaire", +"luni-solaires", +"lyro-guitare", +"là-bas", +"là-contre", +"là-dedans", +"là-delez", +"là-dessous", +"là-dessus", +"là-haut", +"là-pour-ça", +"lâcher-tout", +"læmmer-geyer", +"læmmer-geyers", +"lèche-botta", +"lèche-bottai", +"lèche-bottaient", +"lèche-bottais", +"lèche-bottait", +"lèche-bottant", +"lèche-bottas", +"lèche-bottasse", +"lèche-bottassent", +"lèche-bottasses", +"lèche-bottassiez", +"lèche-bottassions", +"lèche-botte", +"lèche-bottent", +"lèche-botter", +"lèche-bottera", +"lèche-botterai", +"lèche-botteraient", +"lèche-botterais", +"lèche-botterait", +"lèche-botteras", +"lèche-botterez", +"lèche-botteriez", +"lèche-botterions", +"lèche-botterons", +"lèche-botteront", +"lèche-bottes", +"lèche-bottez", +"lèche-bottiez", +"lèche-bottions", +"lèche-bottons", +"lèche-bottâmes", +"lèche-bottât", +"lèche-bottâtes", +"lèche-bottèrent", +"lèche-botté", +"lèche-bottée", +"lèche-bottées", +"lèche-bottés", +"lèche-cul", +"lèche-culs", +"lèche-vitrine", +"lèche-vitrines", +"lèse-majesté", +"lèse-majestés", +"lève-cul", +"lève-culs", +"lève-gazon", +"lève-glace", +"lève-glaces", +"lève-tard", +"lève-tôt", +"lève-vitre", +"lève-vitres", +"légume-feuille", +"légume-fleur", +"légume-fruit", +"légume-racine", +"légume-tige", +"légumes-feuilles", +"légumes-fleurs", +"légumes-fruits", +"légumes-racines", +"légumes-tiges", +"léopard-garou", +"léopards-garous", +"lépisostée-alligator", +"lévi-straussien", +"lévi-straussienne", +"lévi-straussiennes", +"lévi-straussiens", +"lœmmer-geyer", +"lœmmer-geyers", +"m'amie", +"m'as", +"m'as-tu-vu", +"m'as-tu-vue", +"m'as-tu-vues", +"m'as-tu-vus", +"m'bororo", +"m'demma", +"m'enfin", +"m'halla", +"m'hallas", +"m'kahla", +"m'kahlas", +"m'sieur", +"m-commerce", +"m-paiement", +"m-paiements", +"ma'di", +"ma-jong", +"ma-jongs", +"mac-adamisa", +"mac-adamisai", +"mac-adamisaient", +"mac-adamisais", +"mac-adamisait", +"mac-adamisant", +"mac-adamisas", +"mac-adamisasse", +"mac-adamisassent", +"mac-adamisasses", +"mac-adamisassiez", +"mac-adamisassions", +"mac-adamise", +"mac-adamisent", +"mac-adamiser", +"mac-adamisera", +"mac-adamiserai", +"mac-adamiseraient", +"mac-adamiserais", +"mac-adamiserait", +"mac-adamiseras", +"mac-adamiserez", +"mac-adamiseriez", +"mac-adamiserions", +"mac-adamiserons", +"mac-adamiseront", +"mac-adamises", +"mac-adamisez", +"mac-adamisiez", +"mac-adamisions", +"mac-adamisons", +"mac-adamisâmes", +"mac-adamisât", +"mac-adamisâtes", +"mac-adamisèrent", +"mac-adamisé", +"mac-adamisée", +"mac-adamisées", +"mac-adamisés", +"mac-ferlane", +"mac-ferlanes", +"mac-kintosh", +"mac-kintoshs", +"machin-chose", +"machin-choses", +"machin-chouette", +"machine-outil", +"machines-outils", +"machins-chouettes", +"machon-gorgeon", +"magasin-pilote", +"magasins-pilotes", +"magnésio-anthophyllite", +"magnésio-anthophyllites", +"magnésio-axinite", +"magnésio-axinites", +"magnésio-calcite", +"magnésio-calcites", +"magnéto-optique", +"magnéto-optiques", +"magnéto-électrique", +"magnéto-électriques", +"mah-jong", +"mah-jongs", +"mahi-mahi", +"mail-coach", +"mailly-castellois", +"mailly-castelloise", +"mailly-castelloises", +"main-brune", +"main-courante", +"main-d'oeuvre", +"main-d'œuvre", +"main-forte", +"main-militaire", +"maine-anjou", +"mains-courantes", +"mains-d'oeuvre", +"mains-d'œuvre", +"maire-adjoint", +"maires-adjoints", +"maison-mère", +"maisons-mères", +"maitre-autel", +"maitre-chanteur", +"maitre-chien", +"maitre-nageur", +"maitre-nageuse", +"maitres-chiens", +"maitres-nageurs", +"maitres-nageuses", +"maitresse-nageuse", +"maitresses-nageuses", +"make-up", +"make-ups", +"making-of", +"makura-e", +"makura-es", +"mal-aimé", +"mal-aimée", +"mal-aimés", +"mal-baisé", +"mal-baisée", +"mal-baisées", +"mal-baisés", +"mal-comprenant", +"mal-comprenants", +"mal-en-point", +"mal-information", +"mal-informations", +"mal-jugé", +"mal-jugés", +"mal-logement", +"mal-logements", +"mal-peigné", +"mal-peignée", +"mal-pensans", +"mal-pensant", +"mal-pensante", +"mal-pensantes", +"mal-pensants", +"mal-venant", +"mal-venants", +"mal-voyant", +"mal-voyants", +"mal-égal", +"mal-être", +"mal-êtres", +"malayo-polynésien", +"malayo-polynésienne", +"malayo-polynésiennes", +"malayo-polynésiens", +"malgré-nous", +"malle-poste", +"mals-peignées", +"mals-peignés", +"malécite-passamaquoddy", +"mam'selle", +"mam'selles", +"mam'zelle", +"mam'zelles", +"mamie-boomeuse", +"mamie-boomeuses", +"mamy-boomeuse", +"mamy-boomeuses", +"man-bun", +"man-buns", +"manche-à-balle", +"manche-à-balles", +"manco-liste", +"manco-listes", +"mandant-dépendant", +"mandat-carte", +"mandat-cash", +"mandat-lettre", +"mandat-poste", +"mandats-cartes", +"mandats-cash", +"mandats-lettres", +"mandats-poste", +"manganico-potassique", +"mangano-ankérite", +"mangano-ankérites", +"mangano-phlogopite", +"mangano-phlogopites", +"manganoso-ammonique", +"mange-Canayen", +"mange-debout", +"mange-disque", +"mange-disques", +"mange-merde", +"mange-piles", +"mange-tout", +"maniaco-dépressif", +"maniaco-dépressifs", +"maniaco-dépressive", +"maniaco-dépressives", +"mappe-monde", +"mappes-mondes", +"marche-palier", +"marché-gare", +"marché-gares", +"marco-lucanien", +"marco-lucanienne", +"marco-lucaniennes", +"marco-lucaniens", +"margarino-sulfurique", +"margis-chef", +"margis-chefs", +"mariage-sacrement", +"marie-chantal", +"marie-chantalerie", +"marie-chantaleries", +"marie-couche-toi-là", +"marie-galante", +"marie-galantes", +"marie-jeanne", +"marie-jeannes", +"marie-louise", +"marie-louises", +"marie-monastérien", +"marie-monastérienne", +"marie-monastériennes", +"marie-monastériens", +"marie-montois", +"marie-montoise", +"marie-montoises", +"marie-salope", +"marie-trintigner", +"maries-salopes", +"marin-pêcheur", +"marins-pêcheurs", +"marka-dafing", +"marno-bitumineux", +"marno-calcaire", +"marno-calcaires", +"marque-ombrelle", +"marque-page", +"marque-pagea", +"marque-pageai", +"marque-pageaient", +"marque-pageais", +"marque-pageait", +"marque-pageant", +"marque-pageas", +"marque-pageasse", +"marque-pageassent", +"marque-pageasses", +"marque-pageassiez", +"marque-pageassions", +"marque-pagent", +"marque-pageons", +"marque-pager", +"marque-pagera", +"marque-pagerai", +"marque-pageraient", +"marque-pagerais", +"marque-pagerait", +"marque-pageras", +"marque-pagerez", +"marque-pageriez", +"marque-pagerions", +"marque-pagerons", +"marque-pageront", +"marque-pages", +"marque-pagez", +"marque-pageâmes", +"marque-pageât", +"marque-pageâtes", +"marque-pagiez", +"marque-pagions", +"marque-pagèrent", +"marque-pagé", +"marque-pagée", +"marque-pagées", +"marque-pagés", +"marque-produit", +"marque-produits", +"marques-ombrelles", +"marte-piquant", +"marte-piquants", +"marteau-de-mer", +"marteau-pilon", +"marteau-piqueur", +"marteaux-pilons", +"marteaux-piqueurs", +"martin-bâton", +"martin-bâtons", +"martin-chasseur", +"martin-pêcheur", +"martin-sec", +"martin-sire", +"martin-sucré", +"martins-chasseurs", +"martins-pêcheurs", +"martins-sires", +"martins-sucrés", +"martre-zibeline", +"martres-zibelines", +"marxisme-léninisme", +"marxiste-léniniste", +"marxistes-léninistes", +"maréchal-ferrant", +"maréchaux-ferrans", +"maréchaux-ferrants", +"mas-chélyen", +"mas-chélyenne", +"mas-chélyennes", +"mas-chélyens", +"mas-tençois", +"mas-tençoise", +"mas-tençoises", +"masa'il", +"masa'ils", +"mass-média", +"mass-médias", +"masseur-kinésithérapeute", +"masseurs-kinésithérapeutes", +"masseuse-kinésithérapeute", +"masseuses-kinésithérapeutes", +"materno-infantile", +"materno-infantiles", +"mathématico-informatique", +"mathématico-informatiques", +"matthéo-lucanien", +"matthéo-lucanienne", +"matthéo-lucaniennes", +"matthéo-lucaniens", +"mauritano-marocain", +"mauritano-sénégalais", +"maxillo-dentaire", +"maxillo-facial", +"maxillo-labial", +"maxillo-musculaire", +"maël-carhaisien", +"maël-carhaisienne", +"maël-carhaisiennes", +"maël-carhaisiens", +"maître-assistant", +"maître-autel", +"maître-bau", +"maître-chanteur", +"maître-chanteuse", +"maître-chien", +"maître-cylindre", +"maître-jacques", +"maître-mot", +"maître-nageur", +"maître-nageuse", +"maîtres-assistants", +"maîtres-autels", +"maîtres-chanteurs", +"maîtres-chanteuses", +"maîtres-chiens", +"maîtres-cylindres", +"maîtres-jacques", +"maîtres-mots", +"maîtres-nageurs", +"maîtres-nageuses", +"maîtresse-femme", +"maîtresse-nageuse", +"maîtresses-femmes", +"maîtresses-nageuses", +"mea-culpa", +"mele-fila", +"membrano-calcaire", +"menthe-coq", +"menuisier-moulurier", +"mercuroso-mercurique", +"merisier-pays", +"merisiers-pays", +"mets-en", +"metz-tesseran", +"metz-tesseranne", +"metz-tesserannes", +"metz-tesserans", +"meurt-de-faim", +"meurt-de-soif", +"meurt-la-faim", +"meuse-rhin-yssel", +"mezzo-soprano", +"mezzo-sopranos", +"mezzo-termine", +"mezzo-tinto", +"meâ-culpâ", +"miam-miam", +"miaou-miaou", +"michel-angélesque", +"michel-angélesques", +"microélectron-volt", +"microélectron-volts", +"midi-chlorien", +"midi-chloriens", +"midi-pelle", +"midi-pelles", +"midi-pyrénéen", +"mieux-disant", +"mieux-disante", +"mieux-disantes", +"mieux-disants", +"mieux-être", +"militaro-bureaucratique", +"militaro-bureaucratiques", +"militaro-industriel", +"militaro-industrielle", +"militaro-industrielles", +"militaro-industriels", +"milk-bar", +"milk-bars", +"milk-shake", +"milk-shakes", +"mille-au-godet", +"mille-canton", +"mille-feuille", +"mille-feuilles", +"mille-fleurs", +"mille-pattes", +"mille-pertuis", +"mille-pieds", +"mille-points", +"milli-ohm", +"milli-ohms", +"milli-électron-volt", +"milli-électron-volts", +"milliampère-heure", +"milliampères-heures", +"milliélectron-volt", +"milliélectron-volts", +"mime-acrobate", +"ministre-présidence", +"ministre-présidences", +"ministre-président", +"ministres-présidents", +"minn'gotain", +"minn'gotaine", +"minn'gotaines", +"minn'gotains", +"minus-habens", +"minute-lumière", +"minutes-lumière", +"mire-oeuf", +"mire-oeufs", +"mire-œuf", +"mire-œufs", +"miro-miro", +"mixed-border", +"mixti-unibinaire", +"mobil-home", +"mobil-homes", +"modern-style", +"modèle-vue-contrôleur", +"mofu-gudur", +"moi-même", +"moins-disant", +"moins-disants", +"moins-que-rien", +"moins-value", +"moins-values", +"mois-homme", +"mois-hommes", +"mois-lumière", +"moissonner-battre", +"moissonneuse-batteuse", +"moissonneuse-lieuse", +"moissonneuses-batteuses", +"moissonneuses-lieuses", +"moite-moite", +"moitié-moitié", +"mojeño-ignaciano", +"mojeño-javierano", +"mojeño-loretano", +"mojeño-trinitario", +"mollo-mollo", +"moment-clé", +"moment-clés", +"moments-clés", +"monnaie-du-pape", +"monsieur-dame", +"monte-au-ciel", +"monte-charge", +"monte-charges", +"monte-courroie", +"monte-courroies", +"monte-en-l'air", +"monte-escalier", +"monte-escaliers", +"monte-jus", +"monte-lait", +"monte-meuble", +"monte-meubles", +"monte-pente", +"monte-pentes", +"monte-plat", +"monte-plats", +"monti-corcellois", +"monti-corcelloise", +"monti-corcelloises", +"montis-fagussin", +"montis-fagussine", +"montis-fagussines", +"montis-fagussins", +"montre-bracelet", +"montre-chronomètre", +"montres-bracelets", +"montres-chronomètres", +"montréalo-centrisme", +"moque-dieu", +"mords-cheval", +"morphine-base", +"mort-aux-rats", +"mort-bois", +"mort-chien", +"mort-de-chien", +"mort-dieu", +"mort-né", +"mort-née", +"mort-nées", +"mort-nés", +"mort-plain", +"mort-plains", +"mort-terrain", +"mort-vivant", +"morte-eau", +"morte-paye", +"morte-payes", +"morte-saison", +"morte-vivante", +"mortes-eaux", +"mortes-payes", +"mortes-saisons", +"mortes-vivantes", +"morts-bois", +"morts-chiens", +"morts-flats", +"morts-terrains", +"morts-vivants", +"moteur-fusée", +"moteurs-fusées", +"moto-cross", +"moto-crotte", +"moto-crottes", +"moto-réducteur", +"moto-réducteurs", +"moto-école", +"moto-écoles", +"mouche-araignée", +"mouche-sans-raison", +"mouche-scorpion", +"mouches-sans-raison", +"mouches-scorpions", +"mouille-bouche", +"moule-bite", +"moule-burnes", +"moule-fesses", +"moules-burnes", +"moulin-mageois", +"moulin-mageoise", +"moulin-mageoises", +"moulin-à-vent", +"moulins-à-vent", +"moustique-tigre", +"moustiques-tigres", +"mouton-noirisa", +"mouton-noirisai", +"mouton-noirisaient", +"mouton-noirisais", +"mouton-noirisait", +"mouton-noirisant", +"mouton-noirisas", +"mouton-noirisasse", +"mouton-noirisassent", +"mouton-noirisasses", +"mouton-noirisassiez", +"mouton-noirisassions", +"mouton-noirise", +"mouton-noirisent", +"mouton-noiriser", +"mouton-noirisera", +"mouton-noiriserai", +"mouton-noiriseraient", +"mouton-noiriserais", +"mouton-noiriserait", +"mouton-noiriseras", +"mouton-noiriserez", +"mouton-noiriseriez", +"mouton-noiriserions", +"mouton-noiriserons", +"mouton-noiriseront", +"mouton-noirises", +"mouton-noirisez", +"mouton-noirisiez", +"mouton-noirisions", +"mouton-noirisons", +"mouton-noirisâmes", +"mouton-noirisât", +"mouton-noirisâtes", +"mouton-noirisèrent", +"mouton-noirisé", +"mouton-noirisée", +"mouton-noirisées", +"mouton-noirisés", +"mouve-chaux", +"moyens-ducs", +"mu'ugalavyáni", +"mu-métal", +"muco-pus", +"mud-minnow", +"mule-jenny", +"mull-jenny", +"multiplate-forme", +"multiplates-formes", +"mur-rideau", +"murnau-werdenfels", +"murs-rideaux", +"musculo-cutané", +"musettes-repas", +"music-hall", +"music-hallesque", +"music-hallesques", +"music-halls", +"mâche-bouchons", +"mâche-dru", +"mâche-laurier", +"mâle-stérile", +"mâle-stériles", +"mâles-stériles", +"mère-grand", +"mères-grand", +"mètre-ruban", +"mètres-ruban", +"mécoprop-P", +"médecine-ball", +"médecine-balls", +"médio-dorsal", +"médio-européen", +"médio-européenne", +"médio-européennes", +"médio-européens", +"médio-jurassique", +"médio-jurassiques", +"médio-latin", +"médio-latine", +"médio-latines", +"médio-latins", +"médio-océanique", +"médio-océaniques", +"médiéval-fantastique", +"médiévale-fantastique", +"médiévales-fantastiques", +"médiévaux-fantastiques", +"méduse-boite", +"méduse-boîte", +"méduses-boites", +"méduses-boîtes", +"méfenpyr-diéthyl", +"méga-ampère", +"méga-ampères", +"méga-herbivore", +"méga-herbivores", +"méga-océan", +"méga-océans", +"méga-ohm", +"méga-ohms", +"méga-église", +"méga-églises", +"méga-électron-volt", +"méga-électron-volts", +"mégalo-martyr", +"mégalo-martyrs", +"mégaélectron-volt", +"mégaélectron-volts", +"mégléno-roumain", +"méli-mélo", +"mélis-mélos", +"ménil-annellois", +"ménil-annelloise", +"ménil-annelloises", +"ménil-gondoyen", +"ménil-gondoyenne", +"ménil-gondoyennes", +"ménil-gondoyens", +"méningo-encéphalite", +"méningo-gastrique", +"méningo-gastriques", +"mépiquat-chlorure", +"mérier-blanc", +"mériers-blancs", +"méso-américain", +"méso-américaine", +"méso-américaines", +"méso-américains", +"méso-diastolique", +"méso-diastoliques", +"méso-hygrophile", +"méso-hygrophiles", +"méso-systolique", +"méso-systoliques", +"mésosulfuron-méthyl-sodium", +"métacarpo-phalangien", +"métalaxyl-M", +"métam-sodium", +"métaphysico-théologo-cosmolo-nigologie", +"métaphysico-théologo-cosmolo-nigologies", +"métatarso-phalangien", +"méthyl-buténol", +"métirame-zinc", +"métro-boulot-dodo", +"météo-dépendant", +"météo-dépendante", +"météo-dépendantes", +"météo-dépendants", +"mêle-tout", +"mêli-mêlo", +"mêlis-mêlos", +"mêlé-cass", +"mêlé-casse", +"mêlé-casses", +"mêlé-cassis", +"n'dama", +"n'damas", +"n'srani", +"n-3", +"n-6", +"n-9", +"n-aire", +"n-aires", +"n-boule", +"n-boules", +"n-butane", +"n-butanes", +"n-butyle", +"n-cube", +"n-cubes", +"n-dimensionnel", +"n-gone", +"n-gones", +"n-gramme", +"n-grammes", +"n-ième", +"n-ièmes", +"n-octaèdre", +"n-octaèdres", +"n-polytope", +"n-polytopes", +"n-simplexe", +"n-simplexes", +"n-sphère", +"n-sphères", +"n-uple", +"n-uples", +"n-uplet", +"n-uplets", +"na-dené", +"na-déné", +"nam-nam", +"nam-nams", +"name-dropping", +"nano-ohm", +"nano-ohms", +"naphtoxy-2-acétamide", +"narco-guérilla", +"narco-guérillas", +"narco-trafiquant", +"narco-trafiquants", +"narco-État", +"narco-États", +"narcotico-âcre", +"naso-génien", +"naso-lobaire", +"naso-lobaires", +"naso-oculaire", +"naso-palatin", +"naso-palpébral", +"naso-sourcilier", +"naso-transversal", +"nat-gadaw", +"nat-gadaws", +"nat-kadaw", +"nat-kadaws", +"national-socialisme", +"national-socialiste", +"nationale-socialiste", +"nationales-socialistes", +"nationaux-socialistes", +"natro-feldspat", +"natro-feldspats", +"natu-majorité", +"nautico-estival", +"navarro-aragonais", +"navarro-labourdin", +"navire-citerne", +"navire-mère", +"navire-usine", +"navire-école", +"navires-citernes", +"navires-mères", +"navires-écoles", +"ne-m'oubliez-pas", +"negro-spiritual", +"negro-spirituals", +"neptuno-plutonien", +"neptuno-plutonienne", +"neptuno-plutoniens", +"nerf-ferrure", +"nerf-férure", +"net-citoyen", +"net-citoyens", +"nettoie-pipe", +"neuf-berquinois", +"neuf-berquinoise", +"neuf-berquinoises", +"neuf-cents", +"neuro-acoustique", +"neuro-acoustiques", +"neuro-anatomie", +"neuro-anatomies", +"neuro-humoral", +"neuro-humorale", +"neuro-humorales", +"neuro-humoraux", +"neuro-imagerie", +"neuro-imageries", +"neuro-linguistique", +"neuro-linguistiques", +"neuro-musculaire", +"neuro-musculaires", +"neuro-stimulation", +"neuro-végétatif", +"neuro-végétatifs", +"neuro-végétative", +"neuro-végétatives", +"neutro-alcalin", +"neuve-chapellois", +"neuve-chapelloise", +"neuve-chapelloises", +"neuve-grangeais", +"neuve-grangeaise", +"neuve-grangeaises", +"neuville-boscien", +"neuville-boscienne", +"neuville-bosciennes", +"neuville-bosciens", +"neuvy-sautourien", +"neuvy-sautourienne", +"neuvy-sautouriennes", +"neuvy-sautouriens", +"new-yorkais", +"new-yorkaise", +"new-yorkaises", +"new-yorkisa", +"new-yorkisai", +"new-yorkisaient", +"new-yorkisais", +"new-yorkisait", +"new-yorkisant", +"new-yorkisas", +"new-yorkisasse", +"new-yorkisassent", +"new-yorkisasses", +"new-yorkisassiez", +"new-yorkisassions", +"new-yorkise", +"new-yorkisent", +"new-yorkiser", +"new-yorkisera", +"new-yorkiserai", +"new-yorkiseraient", +"new-yorkiserais", +"new-yorkiserait", +"new-yorkiseras", +"new-yorkiserez", +"new-yorkiseriez", +"new-yorkiserions", +"new-yorkiserons", +"new-yorkiseront", +"new-yorkises", +"new-yorkisez", +"new-yorkisiez", +"new-yorkisions", +"new-yorkisons", +"new-yorkisâmes", +"new-yorkisât", +"new-yorkisâtes", +"new-yorkisèrent", +"new-yorkisé", +"new-yorkisée", +"new-yorkisées", +"new-yorkisés", +"newton-mètre", +"newtons-mètres", +"nez-en-cœur", +"nez-percé", +"ngaï-ngaï", +"ngaï-ngaïs", +"ni-ni", +"nian-nian", +"niche-crédence", +"nickel-ankérite", +"nickel-ankérites", +"nickel-magnésite", +"nickel-magnésites", +"nickel-skuttérudite", +"nickel-skuttérudites", +"nid-de-poule", +"night-club", +"night-clubbing", +"night-clubs", +"nigéro-congolais", +"nilo-saharien", +"nilo-saharienne", +"nilo-sahariennes", +"nilo-sahariens", +"nin-nin", +"nippo-américain", +"nippo-américaine", +"nippo-américaines", +"nippo-américains", +"nique-douille", +"nique-douilles", +"nitro-cellulose", +"nitro-celluloses", +"nitro-hydrochlorique", +"nitro-hydrochloriques", +"nitrotal-isopropyl", +"niuafo'ou", +"niuafo'ous", +"nivo-glaciaire", +"nivo-glaciaires", +"nivo-pluvial", +"no-kill", +"no-kills", +"no-poo", +"noie-chien", +"noir-pie", +"noir-pioche", +"noir-pioches", +"noir-ployant", +"noisy-rudignonais", +"noisy-rudignonaise", +"noisy-rudignonaises", +"noli-me-tangere", +"nonante-cinq", +"nonante-deux", +"nonante-et-un", +"nonante-huit", +"nonante-neuf", +"nonante-quatre", +"nonante-sept", +"nonante-six", +"nonante-trois", +"nort-leulinghemois", +"nort-leulinghemoise", +"nort-leulinghemoises", +"nous-même", +"nous-mêmes", +"nouveau-gallois", +"nouveau-né", +"nouveau-née", +"nouveau-nées", +"nouveau-nés", +"nouveau-venu", +"nouveaux-nés", +"nouveaux-venus", +"nouvel-âgeuse", +"nouvel-âgeuses", +"nouvel-âgeux", +"nouvelle-née", +"nouvelle-venue", +"nouvelles-nées", +"nouvelles-venues", +"noyé-d'eau", +"nu-pied", +"nu-pieds", +"nu-propriétaire", +"nu-tête", +"nue-propriétaire", +"nue-propriété", +"nuer-dinka", +"nues-propriétaires", +"nues-propriétés", +"nuit-deboutiste", +"nuit-deboutistes", +"nuoc-mam", +"nuoc-mâm", +"nus-propriétaires", +"nègre-soie", +"nègres-soies", +"nègue-chien", +"nègue-fol", +"néfaste-food", +"néfaste-foods", +"néphro-angiosclérose", +"néphro-angioscléroses", +"néphro-gastrique", +"néphro-urétérectomie", +"néphro-urétérectomies", +"névro-mimosie", +"névro-mimosies", +"nœud-nœud", +"nœuds-nœuds", +"o-ring", +"o-rings", +"occipito-atloïdien", +"occipito-atloïdienne", +"occipito-atloïdiennes", +"occipito-atloïdiens", +"occipito-axoïdien", +"occipito-axoïdienne", +"occipito-axoïdiennes", +"occipito-axoïdiens", +"occipito-cotyloïdien", +"occipito-cotyloïdienne", +"occipito-cotyloïdiennes", +"occipito-cotyloïdiens", +"occipito-frontal", +"occipito-méningien", +"occipito-pariétal", +"occipito-pétreuse", +"occipito-pétreuses", +"occipito-pétreux", +"occipito-sacro-iliaque", +"occipito-sacré", +"occitano-roman", +"octante-deux", +"octante-et-un", +"octante-neuf", +"octo-core", +"octo-cores", +"octo-rotor", +"octo-rotors", +"oculo-motricité", +"oculo-motricités", +"oculo-musculaire", +"oculo-musculaires", +"oculo-zygomatique", +"odonto-stomatologie", +"oeil-de-boeuf", +"oeil-de-chat", +"oeil-de-lièvre", +"oeil-de-paon", +"oeil-de-perdrix", +"oeil-de-pie", +"oeil-de-serpent", +"oeil-de-tigre", +"oeil-du-soleil", +"oeils-de-boeuf", +"oeils-de-chat", +"oeils-de-lièvre", +"oeils-de-paon", +"oeils-de-perdrix", +"oeils-de-pie", +"oeils-de-serpent", +"oeils-de-tigre", +"oesophago-gastro-duodénoscopie", +"oesophago-gastro-duodénoscopies", +"off-market", +"off-shore", +"ogivo-cylindrique", +"ohm-mètre", +"ohms-mètres", +"oie-cygne", +"oiseau-chameau", +"oiseau-cloche", +"oiseau-lyre", +"oiseau-mouche", +"oiseau-papillon", +"oiseau-tonnerre", +"oiseau-trompette", +"oiseau-éléphant", +"oiseaux-chameaux", +"oiseaux-cloches", +"oiseaux-lyres", +"oiseaux-mouches", +"oiseaux-papillons", +"oiseaux-tonnerres", +"oiseaux-trompettes", +"old-ice", +"old-ices", +"oligo-élément", +"oligo-éléments", +"olla-podrida", +"olé-olé", +"oléo-calcaire", +"oléo-calcaires", +"omaha-ponca", +"omaha-poncas", +"omble-chevalier", +"ombre-chevalier", +"ombro-thermique", +"ombro-thermiques", +"omphalo-mésentérique", +"omphalo-mésentériques", +"omphalo-phlébite", +"omphalo-phlébites", +"oméga-3", +"oméga-6", +"oméga-9", +"on-dit", +"one-man-show", +"one-shot", +"one-step", +"one-steps", +"one-woman-show", +"oost-cappelois", +"oost-cappeloise", +"oost-cappeloises", +"opal-AN", +"open-source", +"open-space", +"open-spaces", +"opt-in", +"opt-out", +"opto-strié", +"opéra-comique", +"opéras-comiques", +"or-sol", +"orang-outan", +"orang-outang", +"orangs-outangs", +"orangs-outans", +"orbito-nasal", +"orbito-palpébral", +"oreille-d'abbé", +"oreille-d'ours", +"oreille-d'âne", +"oreille-de-lièvre", +"oreille-de-loup", +"oreille-de-mer", +"oreille-de-souris", +"oreilles-d'ours", +"oreilles-d'âne", +"oreilles-de-mer", +"oreilles-de-souris", +"organo-calcaire", +"organo-calcaires", +"organo-chloré", +"organo-chlorée", +"organo-chlorées", +"organo-chlorés", +"organo-halogéné", +"organo-halogénée", +"organo-halogénées", +"organo-halogénés", +"organo-phosphoré", +"organo-phosphorée", +"organo-phosphorées", +"organo-phosphorés", +"orienteur-marqueur", +"orienté-objet", +"orp-jauchois", +"ortho-sympathique", +"ortho-sympathiques", +"ossau-iraty", +"ossau-iratys", +"ostéo-arthrite", +"ostéo-arthrites", +"oto-rhino", +"oto-rhino-laryngologie", +"oto-rhino-laryngologies", +"oto-rhino-laryngologiste", +"oto-rhino-laryngologistes", +"oto-rhinos", +"ouaf-ouaf", +"oui-da", +"oui-non-bof", +"ouralo-altaïque", +"ouralo-altaïques", +"ours-garou", +"ours-garous", +"ouve-wirquinois", +"ouve-wirquinoise", +"ouve-wirquinoises", +"ouèche-ouèche", +"ouèches-ouèches", +"ouï-dire", +"ouïr-dire", +"ovo-lacto-végétarisme", +"ovo-lacto-végétarismes", +"ovo-urinaire", +"ovo-végétarisme", +"ovo-végétarismes", +"oxidéméton-méthyl", +"oxo-biodégradable", +"oxo-biodégradables", +"oxo-dégradable", +"oxo-dégradables", +"oxy-iodure", +"oxy-iodures", +"oxydo-réduction", +"oxydo-réductions", +"oxydéméton-méthyl", +"p'rlotte", +"p't-être", +"p'tain", +"p'tit", +"p'tite", +"p'tites", +"p'tits", +"p-acétylaminophénol", +"p-adique", +"p-adiques", +"p-graphe", +"p-graphes", +"p.-ê.", +"pH-mètre", +"pa'anga", +"pack-ice", +"pack-ices", +"package-deal", +"package-deals", +"pagano-chrétien", +"page-turner", +"pail-mail", +"paille-en-cul", +"paille-en-queue", +"pailles-en-cul", +"pailles-en-queue", +"pain-beurre", +"pain-d'épicier", +"pain-d'épiciers", +"pain-d'épicière", +"pain-d'épicières", +"pain-de-pourceau", +"pains-de-pourceau", +"pair-programma", +"pair-programmai", +"pair-programmaient", +"pair-programmais", +"pair-programmait", +"pair-programmant", +"pair-programmas", +"pair-programmasse", +"pair-programmassent", +"pair-programmasses", +"pair-programmassiez", +"pair-programmassions", +"pair-programme", +"pair-programment", +"pair-programmer", +"pair-programmera", +"pair-programmerai", +"pair-programmeraient", +"pair-programmerais", +"pair-programmerait", +"pair-programmeras", +"pair-programmerez", +"pair-programmeriez", +"pair-programmerions", +"pair-programmerons", +"pair-programmeront", +"pair-programmes", +"pair-programmez", +"pair-programmiez", +"pair-programmions", +"pair-programmons", +"pair-programmâmes", +"pair-programmât", +"pair-programmâtes", +"pair-programmèrent", +"pair-programmé", +"pair-à-pair", +"pal-fer", +"palato-labial", +"palato-labiale", +"palato-pharyngien", +"palato-pharyngite", +"palato-pharyngites", +"palato-salpingien", +"palato-staphylin", +"palato-staphylins", +"palladico-potassique", +"palmier-chanvre", +"palmier-dattier", +"palmiers-chanvre", +"palmiers-dattiers", +"palpe-mâchoire", +"palu'e", +"palu'es", +"paléo-continental", +"paléo-lac", +"paléo-lacs", +"paléo-reconstruction", +"paléo-reconstructions", +"pama-nyungan", +"pan-européen", +"pan-européenne", +"pan-européennes", +"pan-européens", +"pan-lucanisme", +"pan-mandingue", +"pan-mandingues", +"panchen-lama", +"pancréatico-duodénal", +"panier-repas", +"paniers-repas", +"panpan-cucul", +"panthère-garou", +"panthères-garous", +"papa-gâteau", +"papas-gâteaux", +"papier-caillou-ciseaux", +"papier-calque", +"papier-cul", +"papier-filtre", +"papier-monnaie", +"papiers-calque", +"papy-boom", +"papy-boomer", +"papy-boomers", +"papy-boomeur", +"papy-boomeurs", +"paquet-cadeau", +"paquets-cadeaux", +"par-cœur", +"par-dehors", +"par-delà", +"par-derrière", +"par-dessous", +"par-dessus", +"par-devant", +"par-devers", +"para-acétyl-amino-phénol", +"para-continental", +"para-dichlorobenzène", +"para-légal", +"para-légale", +"para-légales", +"para-légaux", +"parachute-frein", +"parachutes-freins", +"parathion-méthyl", +"parathion-éthyl", +"parc-d'anxtotais", +"parc-d'anxtotaise", +"parc-d'anxtotaises", +"parking-relais", +"parler-pour-ne-rien-dire", +"parotido-auriculaire", +"parotido-auriculaires", +"parti-pris", +"participation-pari", +"particule-dieu", +"particules-dieu", +"parva-pétricien", +"parva-pétricienne", +"parva-pétriciennes", +"parva-pétriciens", +"pas-d'âne", +"pas-de-porte", +"pas-à-pas", +"pascal-seconde", +"pascals-secondes", +"paso-doble", +"paso-dobles", +"passif-agressif", +"passifs-agressifs", +"passing-shot", +"passing-shots", +"patronnier-gradeur", +"patronniers-gradeurs", +"patronnière-gradeuse", +"patronnières-gradeuses", +"patte-d'oie", +"patte-de-lièvre", +"patte-pelu", +"patte-pelus", +"pattes-d'oie", +"pattes-de-lièvre", +"pauci-relationnel", +"pauci-relationnelle", +"pauci-relationnelles", +"pauci-relationnels", +"pauci-spécifique", +"pauci-spécifiques", +"pause-café", +"pause-carrière", +"pause-santé", +"pauses-café", +"pauses-carrière", +"pauses-santé", +"pay-per-view", +"pay-to-win", +"pays-bas", +"payé-emporté", +"pc-banking", +"peau-bleue", +"peau-de-chienna", +"peau-de-chiennai", +"peau-de-chiennaient", +"peau-de-chiennais", +"peau-de-chiennait", +"peau-de-chiennant", +"peau-de-chiennas", +"peau-de-chiennasse", +"peau-de-chiennassent", +"peau-de-chiennasses", +"peau-de-chiennassiez", +"peau-de-chiennassions", +"peau-de-chienne", +"peau-de-chiennent", +"peau-de-chienner", +"peau-de-chiennera", +"peau-de-chiennerai", +"peau-de-chienneraient", +"peau-de-chiennerais", +"peau-de-chiennerait", +"peau-de-chienneras", +"peau-de-chiennerez", +"peau-de-chienneriez", +"peau-de-chiennerions", +"peau-de-chiennerons", +"peau-de-chienneront", +"peau-de-chiennes", +"peau-de-chiennez", +"peau-de-chienniez", +"peau-de-chiennions", +"peau-de-chiennons", +"peau-de-chiennâmes", +"peau-de-chiennât", +"peau-de-chiennâtes", +"peau-de-chiennèrent", +"peau-de-chienné", +"peau-de-chiennée", +"peau-de-chiennées", +"peau-de-chiennés", +"peau-rouge", +"peaux-rouges", +"peer-to-peer", +"peigne-cul", +"peigne-culs", +"peigne-zizi", +"peine-à-jouir", +"peis-coua", +"pele-ata", +"pelle-pioche", +"pelle-à-cul", +"pelles-bêches", +"pelles-pioches", +"pelles-à-cul", +"pelure-d'oignon", +"pelvi-crural", +"pelvi-trochantérien", +"pelvi-trochantérienne", +"pelvi-trochantériennes", +"pelvi-trochantériens", +"pen-testeur", +"pen-testeurs", +"pen-testeuse", +"pen-testeuses", +"pen-ty", +"pencak-silat", +"penn-ty", +"pense-bête", +"pense-bêtes", +"penta-continental", +"penta-core", +"penta-cores", +"penta-cœur", +"penta-cœurs", +"people-isa", +"people-isai", +"people-isaient", +"people-isais", +"people-isait", +"people-isant", +"people-isas", +"people-isasse", +"people-isassent", +"people-isasses", +"people-isassiez", +"people-isassions", +"people-ise", +"people-isent", +"people-iser", +"people-isera", +"people-iserai", +"people-iseraient", +"people-iserais", +"people-iserait", +"people-iseras", +"people-iserez", +"people-iseriez", +"people-iserions", +"people-iserons", +"people-iseront", +"people-ises", +"people-isez", +"people-isiez", +"people-isions", +"people-isons", +"people-isâmes", +"people-isât", +"people-isâtes", +"people-isèrent", +"people-isé", +"people-isée", +"people-isées", +"people-isés", +"perche-brochet", +"perche-soleil", +"perd-sa-queue", +"perd-tout", +"perdant-perdant", +"perdante-perdante", +"perdantes-perdantes", +"perdants-perdants", +"perfo-vérif", +"perroquet-hibou", +"perroquets-hiboux", +"perruche-moineau", +"perruches-moineaux", +"pesco-végétarien", +"pet'che", +"pet-d'âne", +"pet-de-loup", +"pet-de-nonne", +"pet-de-soeur", +"pet-de-sœur", +"pet-en-l'air", +"petites-bourgeoises", +"petites-bourgeoisies", +"petites-filles", +"petites-mains", +"petites-maîtresses", +"petites-nièces", +"petites-russes", +"petits-beurre", +"petits-bourgeois", +"petits-chênes", +"petits-ducs", +"petits-déjeuners", +"petits-enfants", +"petits-fils", +"petits-fours", +"petits-gris", +"petits-laits", +"petits-maîtres", +"petits-neveux", +"petits-russes", +"petits-suisses", +"petits-trains", +"pets-de-loup", +"pets-de-nonne", +"peul-peul", +"peut-être", +"pharyngo-laryngite", +"pharyngo-laryngites", +"pharyngo-staphylin", +"philosopho-théologique", +"philosopho-théologiques", +"phonético-symbolique", +"phoque-garou", +"phoque-léopard", +"phoques-garous", +"phosphate-allophane", +"phosphate-allophanes", +"phoséthyl-Al", +"phosétyl-Al", +"photos-finish", +"phragmito-scirpaie", +"phragmito-scirpaies", +"phrase-clé", +"phrases-clés", +"phréno-glottisme", +"phréno-glottismes", +"physico-chimie", +"physico-chimies", +"physico-chimique", +"physico-chimiques", +"physico-mathématique", +"physico-mathématiques", +"physio-pathologie", +"physio-pathologies", +"phénico-punique", +"phénico-puniques", +"pian's", +"piane-piane", +"piano-bar", +"piano-bars", +"piano-forte", +"piano-fortes", +"piano-manivelle", +"pic-vert", +"pic-verts", +"pichot-chêne", +"pichots-chênes", +"pick-up", +"pick-ups", +"pico-condensateur", +"pico-condensateurs", +"pico-ohm", +"pico-ohms", +"pics-verts", +"pidgin-english", +"pie-grièche", +"pie-mère", +"pie-noir", +"pie-noire", +"pie-noires", +"pie-noirs", +"pie-rouge", +"pied-bot", +"pied-d'alouette", +"pied-d'oiseau", +"pied-d'étape", +"pied-de-banc", +"pied-de-biche", +"pied-de-boeuf", +"pied-de-bœuf", +"pied-de-chat", +"pied-de-cheval", +"pied-de-chèvre", +"pied-de-coq", +"pied-de-corbeau", +"pied-de-griffon", +"pied-de-lion", +"pied-de-loup", +"pied-de-mouche", +"pied-de-mouton", +"pied-de-pigeon", +"pied-de-poule", +"pied-de-pélican", +"pied-de-veau", +"pied-droit", +"pied-fort", +"pied-noir", +"pied-noire", +"pied-noirisa", +"pied-noirisai", +"pied-noirisaient", +"pied-noirisais", +"pied-noirisait", +"pied-noirisant", +"pied-noirisas", +"pied-noirisasse", +"pied-noirisassent", +"pied-noirisasses", +"pied-noirisassiez", +"pied-noirisassions", +"pied-noirise", +"pied-noirisent", +"pied-noiriser", +"pied-noirisera", +"pied-noiriserai", +"pied-noiriseraient", +"pied-noiriserais", +"pied-noiriserait", +"pied-noiriseras", +"pied-noiriserez", +"pied-noiriseriez", +"pied-noiriserions", +"pied-noiriserons", +"pied-noiriseront", +"pied-noirises", +"pied-noirisez", +"pied-noirisiez", +"pied-noirisions", +"pied-noirisons", +"pied-noirisâmes", +"pied-noirisât", +"pied-noirisâtes", +"pied-noirisèrent", +"pied-noirisé", +"pied-noirisée", +"pied-noirisées", +"pied-noirisés", +"pied-plat", +"pied-rouge", +"pied-tendre", +"pied-vert", +"pied-à-terre", +"pieds-bots", +"pieds-d'alouette", +"pieds-d'oiseau", +"pieds-de-biche", +"pieds-de-boeuf", +"pieds-de-bœuf", +"pieds-de-chat", +"pieds-de-chèvre", +"pieds-de-coq", +"pieds-de-corbeau", +"pieds-de-griffon", +"pieds-de-lion", +"pieds-de-mouche", +"pieds-de-mouton", +"pieds-de-veau", +"pieds-droits", +"pieds-forts", +"pieds-noires", +"pieds-noirs", +"pieds-paquets", +"pieds-plats", +"pieds-tendres", +"pierre-buffiérois", +"pierre-buffiéroise", +"pierre-buffiéroises", +"pierre-bénitain", +"pierre-bénitaine", +"pierre-bénitaines", +"pierre-bénitains", +"pierre-châtelois", +"pierre-châteloise", +"pierre-châteloises", +"pierre-feuille-ciseaux", +"pierre-levéen", +"pierre-levéenne", +"pierre-levéennes", +"pierre-levéens", +"pierre-montois", +"pierre-montoise", +"pierre-montoises", +"pierre-papier-ciseaux", +"pierre-qui-vire", +"pierres-qui-virent", +"pies-grièches", +"pies-mères", +"pile-poil", +"pilo-sébacé", +"pin's", +"pin-pon", +"pin-up", +"pince-balle", +"pince-balles", +"pince-fesse", +"pince-fesses", +"pince-lisière", +"pince-maille", +"pince-mailles", +"pince-monseigneur", +"pince-nez", +"pince-notes", +"pince-oreille", +"pince-oreilles", +"pince-sans-rire", +"pince-érigne", +"pince-érignes", +"pinces-monseigneur", +"ping-pong", +"ping-pongs", +"pino-balméen", +"pino-balméenne", +"pino-balméennes", +"pino-balméens", +"pins-justarétois", +"pins-justarétoise", +"pins-justarétoises", +"piou-piou", +"piou-pious", +"pipe-line", +"pipe-lines", +"piqueur-suceur", +"pirimiphos-méthyl", +"pirimiphos-éthyl", +"pis-aller", +"pis-allers", +"pisse-au-lit", +"pisse-chien", +"pisse-chiens", +"pisse-copie", +"pisse-copies", +"pisse-debout", +"pisse-froid", +"pisse-mémère", +"pisse-mémé", +"pisse-sang", +"pisse-trois-gouttes", +"pisse-vinaigre", +"pisse-vinaigres", +"pisse-z-yeux", +"pissy-pôvillais", +"pissy-pôvillaise", +"pissy-pôvillaises", +"pistillo-staminé", +"pistolet-mitrailleur", +"pistolets-mitrailleurs", +"pit-bulls", +"pixie-bob", +"pièces-au-cul", +"piège-à-cons", +"pièges-à-cons", +"pié-de-lion", +"piés-de-lion", +"piétin-verse", +"piétin-échaudage", +"piézo-électricité", +"piézo-électricités", +"piézo-électrique", +"piézo-électriques", +"plachy-buyonnais", +"plachy-buyonnaise", +"plachy-buyonnaises", +"plain-chant", +"plain-pied", +"plains-chants", +"plains-pieds", +"plan-masse", +"plan-plan", +"plan-planisme", +"plan-planismes", +"plan-socialisa", +"plan-socialisai", +"plan-socialisaient", +"plan-socialisais", +"plan-socialisait", +"plan-socialisant", +"plan-socialisas", +"plan-socialisasse", +"plan-socialisassent", +"plan-socialisasses", +"plan-socialisassiez", +"plan-socialisassions", +"plan-socialise", +"plan-socialisent", +"plan-socialiser", +"plan-socialisera", +"plan-socialiserai", +"plan-socialiseraient", +"plan-socialiserais", +"plan-socialiserait", +"plan-socialiseras", +"plan-socialiserez", +"plan-socialiseriez", +"plan-socialiserions", +"plan-socialiserons", +"plan-socialiseront", +"plan-socialises", +"plan-socialisez", +"plan-socialisiez", +"plan-socialisions", +"plan-socialisons", +"plan-socialisâmes", +"plan-socialisât", +"plan-socialisâtes", +"plan-socialisèrent", +"plan-socialisé", +"plan-socialisée", +"plan-socialisées", +"plan-socialisés", +"plan-séquence", +"plan-séquences", +"planches-contacts", +"plans-masses", +"plans-séquences", +"plante-crayon", +"plante-éponge", +"plantes-crayons", +"plaque-bière", +"plaque-tonnerre", +"plat-bord", +"plat-cul", +"plat-culs", +"plat-de-bierre", +"plate-bande", +"plate-bière", +"plate-face", +"plate-forme", +"plate-longe", +"plateau-repas", +"plateaux-repas", +"plates-bandes", +"plates-formes", +"plates-longes", +"platinico-ammonique", +"plats-bords", +"play-back", +"play-backs", +"play-boy", +"play-boys", +"play-off", +"play-offs", +"plaît-il", +"plein-cintre", +"plein-emploi", +"pleine-fougerais", +"pleine-fougeraise", +"pleine-fougeraises", +"pleins-cintres", +"plessis-ansoldien", +"plessis-ansoldienne", +"plessis-ansoldiennes", +"plessis-ansoldiens", +"plessis-brionnais", +"plessis-brionnaise", +"plessis-brionnaises", +"plessis-bucardésien", +"plessis-bucardésienne", +"plessis-bucardésiennes", +"plessis-bucardésiens", +"plessis-episcopien", +"plessis-episcopienne", +"plessis-episcopiennes", +"plessis-episcopiens", +"plessis-grammoirien", +"plessis-grammoirienne", +"plessis-grammoiriennes", +"plessis-grammoiriens", +"plessis-luzarchois", +"plessis-luzarchoise", +"plessis-luzarchoises", +"plessis-macéen", +"plessis-macéenne", +"plessis-macéennes", +"plessis-macéens", +"plessis-épiscopien", +"plessis-épiscopienne", +"plessis-épiscopiennes", +"plessis-épiscopiens", +"pleu-pleu", +"pleure-misère", +"pleure-misères", +"pleuro-péricardite", +"pleuronecte-guitare", +"plieuse-inséreuse", +"plieuses-inséreuses", +"plongée-spéléo", +"plongées-spéléo", +"plouezoc'hois", +"plouezoc'hoise", +"plouezoc'hoises", +"ploulec'hois", +"ploulec'hoise", +"ploulec'hoises", +"plounéour-trezien", +"plounéour-trezienne", +"plounéour-treziennes", +"plounéour-treziens", +"ploye-ressort", +"plui-plui", +"plum-cake", +"plum-cakes", +"plum-pudding", +"plumbo-aragonite", +"plumbo-aragonites", +"plume-couteau", +"plumes-couteaux", +"pluri-continental", +"pluri-interprétable", +"pluri-interprétables", +"pluri-journalier", +"pluri-modal", +"pluri-national", +"pluri-nationale", +"pluri-nationales", +"pluri-nationaux", +"plus-d'atouts", +"plus-disant", +"plus-part", +"plus-payé", +"plus-produit", +"plus-produits", +"plus-pétition", +"plus-que-parfait", +"plus-que-parfaits", +"plus-value", +"plus-values", +"pluto-neptunien", +"pluvier-hirondelle", +"plû-part", +"poche-cuiller", +"poche-revolver", +"poches-revolver", +"pochette-surprise", +"pochettes-surprise", +"pochettes-surprises", +"podio-régalien", +"podio-régalienne", +"podio-régaliennes", +"podio-régaliens", +"podo-orthésiste", +"podo-orthésistes", +"poggio-mezzanais", +"poggio-mezzanaise", +"poggio-mezzanaises", +"pogne-cul", +"pogne-culs", +"poids-lourd", +"poids-lourds", +"point-arrière", +"point-col", +"point-milieu", +"point-selle", +"point-virgule", +"point-voyelle", +"pointe-de-coeur", +"pointe-de-cœur", +"pointe-de-diamant", +"pointe-noirais", +"pointe-noiraise", +"pointe-noiraises", +"pointer-et-cliquer", +"pointes-de-coeur", +"pointes-de-cœur", +"pointes-de-diamant", +"points-virgules", +"points-voyelles", +"poissonnier-écailler", +"poitevin-saintongeais", +"poivre-sel", +"poix-résine", +"poka-yoké", +"politico-idéologique", +"politico-idéologiques", +"politico-médiatique", +"politico-religieuse", +"politico-religieuses", +"politico-religieux", +"politico-économique", +"politico-économiques", +"pollueur-payeur", +"pollueurs-payeurs", +"poly-articulaire", +"poly-articulaires", +"poly-insaturé", +"poly-insaturée", +"poly-insaturées", +"poly-insaturés", +"poly-sexuel", +"poly-sexuelle", +"polychlorodibenzo-p-dioxine", +"polychlorodibenzo-p-dioxines", +"pomme-de-pin", +"pomme-grenade", +"pommes-de-pin", +"pompage-turbinage", +"pompages-turbinages", +"ponts-bascules", +"ponts-canaux", +"ponts-de-céais", +"ponts-de-céaise", +"ponts-de-céaises", +"ponts-levis", +"ponts-neufs", +"pop-corn", +"pop-in", +"pop-ins", +"pop-punk", +"pop-up", +"pop-ups", +"popa'a", +"porc-épic", +"porcs-épics", +"portes-fenêtres", +"portes-tambour", +"porteur-de-peau", +"porto-vecchiais", +"porto-vecchiaise", +"porto-vecchiaises", +"portrait-charge", +"portrait-robot", +"portraits-charges", +"portraits-robots", +"pose-tubes", +"post-11-Septembre", +"posé-décollé", +"posé-décollés", +"pot-au-feu", +"pot-au-noir", +"pot-beurrier", +"pot-bouille", +"pot-de-vin", +"pot-en-tête", +"pot-pourri", +"potassico-ammonique", +"potassico-mercureux", +"poto-poto", +"potron-jacquet", +"potron-minet", +"pots-de-vin", +"pots-pourris", +"pou-de-soie", +"pouce-pied", +"pouces-pieds", +"poudre-éclair", +"poudres-éclair", +"poudres-éclairs", +"pouligny-saint-pierre", +"poult-de-soie", +"poults-de-soie", +"pour-boire", +"pour-cent", +"pourri-gâté", +"poursuite-bâillon", +"pousse-au-crime", +"pousse-au-jouir", +"pousse-au-vice", +"pousse-broche", +"pousse-broches", +"pousse-café", +"pousse-cafés", +"pousse-caillou", +"pousse-cailloux", +"pousse-cambrure", +"pousse-cambrures", +"pousse-cul", +"pousse-culs", +"pousse-fiche", +"pousse-goupille", +"pousse-mégot", +"pousse-mégots", +"pousse-navette", +"pousse-pied", +"pousse-pieds", +"pousse-pointe", +"pousse-pointes", +"pousse-pousse", +"pout-de-soie", +"pouts-de-soie", +"poux-de-soie", +"pouy-roquelain", +"pouy-roquelaine", +"pouy-roquelaines", +"pouy-roquelains", +"pouët-pouët", +"pow-wow", +"pow-wows", +"poët-lavalien", +"poët-lavalienne", +"poët-lavaliennes", +"poët-lavaliens", +"premier-ministra", +"premier-ministrai", +"premier-ministraient", +"premier-ministrais", +"premier-ministrait", +"premier-ministrant", +"premier-ministras", +"premier-ministrasse", +"premier-ministrassent", +"premier-ministrasses", +"premier-ministrassiez", +"premier-ministrassions", +"premier-ministre", +"premier-ministrent", +"premier-ministrer", +"premier-ministrera", +"premier-ministrerai", +"premier-ministreraient", +"premier-ministrerais", +"premier-ministrerait", +"premier-ministreras", +"premier-ministrerez", +"premier-ministreriez", +"premier-ministrerions", +"premier-ministrerons", +"premier-ministreront", +"premier-ministres", +"premier-ministrez", +"premier-ministriez", +"premier-ministrions", +"premier-ministrons", +"premier-ministrâmes", +"premier-ministrât", +"premier-ministrâtes", +"premier-ministrèrent", +"premier-ministré", +"premier-ministrée", +"premier-ministrées", +"premier-ministrés", +"premier-né", +"premiers-nés", +"presqu'accident", +"presqu'accidents", +"presqu'ile", +"presqu'iles", +"presqu'île", +"presqu'îles", +"press-book", +"press-books", +"presse-agrume", +"presse-agrumes", +"presse-ail", +"presse-artère", +"presse-artères", +"presse-citron", +"presse-citrons", +"presse-fruits", +"presse-légumes", +"presse-papier", +"presse-papiers", +"presse-purée", +"presse-purées", +"presse-urètre", +"presse-urètres", +"presse-étoffe", +"presse-étoffes", +"presse-étoupe", +"presse-étoupes", +"pressignaco-vicois", +"pressignaco-vicoise", +"pressignaco-vicoises", +"preux-romanien", +"preux-romanienne", +"preux-romaniennes", +"preux-romaniens", +"prie-Dieu", +"prim'holstein", +"prima-mensis", +"prime-sautier", +"prince-président", +"prince-sans-rire", +"prince-édouardien", +"prince-édouardienne", +"prince-édouardiennes", +"prince-édouardiens", +"prince-électeur", +"princes-présidents", +"princes-électeurs", +"prisons-écoles", +"privat-docent", +"privat-docentisme", +"privat-docentismes", +"prix-choc", +"prix-chocs", +"programme-cadre", +"programmes-cadres", +"prohexadione-calcium", +"promis-juré", +"promis-jurée", +"promis-jurées", +"promis-jurés", +"promène-couillon", +"promène-couillons", +"pronom-adjectif", +"pronoms-adjectifs", +"propre-à-rien", +"propres-à-rien", +"prostato-péritonéal", +"prostato-péritonéale", +"prostato-péritonéales", +"prostato-péritonéaux", +"protège-cahier", +"protège-cahiers", +"protège-dent", +"protège-dents", +"protège-mamelon", +"protège-mamelons", +"protège-oreille", +"protège-oreilles", +"protège-slip", +"protège-slips", +"protège-tibia", +"protège-tibias", +"prout-prout", +"prout-proute", +"prout-proutes", +"prout-prouts", +"prud'homal", +"prud'homale", +"prud'homales", +"prud'homaux", +"prud'homie", +"prud'homies", +"prunet-puigois", +"prunet-puigoise", +"prunet-puigoises", +"prunier-cerise", +"pruniers-cerises", +"prés-bois", +"prés-salés", +"prés-vergers", +"président-candidat", +"présidente-candidate", +"présidentes-candidates", +"présidents-candidats", +"présidents-directeurs", +"prêt-à-monter", +"prêt-à-penser", +"prêt-à-porter", +"prêt-à-poster", +"prête-nom", +"prête-noms", +"prêtres-ouvriers", +"prêts-à-penser", +"prêts-à-porter", +"prône-misère", +"pschitt-pschitt", +"psycho-physiologique", +"psycho-physiologiques", +"psycho-physique", +"psycho-physiques", +"psycho-pop", +"ptérygo-pharyngien", +"pub-restaurant", +"pub-restaurants", +"puce-chique", +"puces-chiques", +"pue-la-sueur", +"puis-je", +"puiset-doréen", +"puiset-doréenne", +"puiset-doréennes", +"puiset-doréens", +"pull-buoy", +"pull-buoys", +"pull-over", +"pull-overs", +"pull-up", +"pulmo-aortique", +"pulso-réacteurs", +"pulvérisateur-mélangeur", +"punaise-mouche", +"punaises-mouches", +"punching-ball", +"punching-balls", +"punkah-wallah", +"pur-sang", +"pur-sangs", +"pure-laine", +"purge-mariage", +"purge-mariages", +"purs-sangs", +"push-back", +"push-up", +"putot-bessinois", +"putot-bessinoise", +"putot-bessinoises", +"pyraflufen-éthyl", +"pyrimiphos-méthyl", +"pyrimiphos-éthyl", +"pyro-électricité", +"pyro-électricités", +"pyro-électrique", +"pyro-électriques", +"pâtissier-chocolatier", +"père-la-pudeur", +"pères-la-pudeur", +"pèse-acide", +"pèse-acides", +"pèse-alcool", +"pèse-alcools", +"pèse-bébé", +"pèse-bébés", +"pèse-esprit", +"pèse-esprits", +"pèse-lait", +"pèse-laits", +"pèse-lettre", +"pèse-lettres", +"pèse-liqueur", +"pèse-liqueurs", +"pèse-mout", +"pèse-mouts", +"pèse-moût", +"pèse-moûts", +"pèse-nitre", +"pèse-nitres", +"pèse-personne", +"pèse-personnes", +"pèse-sel", +"pèse-sels", +"pèse-sirop", +"pèse-sirops", +"pèse-vernis", +"pète-sec", +"pète-secs", +"pète-sèche", +"pète-sèches", +"pédal'eau", +"pédicure-podologue", +"pédicures-podologues", +"pénicillino-résistance", +"pénicillino-résistances", +"pénicillino-sensibilité", +"pénicillino-sensibilités", +"péronéo-calcanéen", +"péronéo-malléolaire", +"péronéo-malléolaires", +"péronéo-phalangien", +"péronéo-tibial", +"péta-ampère", +"péta-ampères", +"péta-électron-volt", +"péta-électron-volts", +"pétaélectron-volt", +"pétaélectron-volts", +"pétro-monarchie", +"pétro-monarchies", +"pétro-occipital", +"pétro-salpingo-staphylin", +"pétro-salpingo-staphylins", +"pétro-staphylin", +"pétrolier-minéralier", +"pétrus-colien", +"pétrus-colienne", +"pétrus-coliennes", +"pétrus-coliens", +"pêche-bernard", +"pêche-bernards", +"q'anjob'al", +"qu-in-situ", +"quad-core", +"quad-cores", +"quadri-accélération", +"quadri-accélérationnellement", +"quadri-ailé", +"quadri-couche", +"quadri-couches", +"quadri-courant", +"quadri-dimensionnel", +"quadri-dimensionnelle", +"quadri-dimensionnelles", +"quadri-dimensionnels", +"quadri-rotor", +"quadri-rotors", +"quadruple-croche", +"quadruples-croches", +"quant-à-moi", +"quant-à-soi", +"quarante-cinq", +"quarante-deux", +"quarante-douze", +"quarante-et-un", +"quarante-et-une", +"quarante-huit", +"quarante-huitard", +"quarante-huitarde", +"quarante-huitardes", +"quarante-huitards", +"quarante-huitième", +"quarante-huitièmes", +"quarante-langues", +"quarante-neuf", +"quarante-neuvième", +"quarante-neuvièmes", +"quarante-quatre", +"quarante-sept", +"quarante-six", +"quarante-trois", +"quarante-vingt", +"quart-arrière", +"quart-biscuité", +"quart-d'heure", +"quart-de-cercle", +"quart-de-finaliste", +"quart-de-finalistes", +"quart-de-pouce", +"quart-monde", +"quart-temps", +"quarte-fagot", +"quartier-général", +"quartier-maitre", +"quartier-maitres", +"quartier-maître", +"quartier-mestre", +"quartiers-maîtres", +"quarts-arrières", +"quarts-de-cercle", +"quat'z'arts", +"quatorze-marsiste", +"quatorze-marsistes", +"quatre-cent-vingt-et-un", +"quatre-chevaux", +"quatre-cinq-un", +"quatre-cornes", +"quatre-de-chiffre", +"quatre-feuilles", +"quatre-heura", +"quatre-heurai", +"quatre-heuraient", +"quatre-heurais", +"quatre-heurait", +"quatre-heurant", +"quatre-heuras", +"quatre-heurasse", +"quatre-heurassent", +"quatre-heurasses", +"quatre-heurassiez", +"quatre-heurassions", +"quatre-heure", +"quatre-heurent", +"quatre-heurer", +"quatre-heurera", +"quatre-heurerai", +"quatre-heureraient", +"quatre-heurerais", +"quatre-heurerait", +"quatre-heureras", +"quatre-heurerez", +"quatre-heureriez", +"quatre-heurerions", +"quatre-heurerons", +"quatre-heureront", +"quatre-heures", +"quatre-heurez", +"quatre-heuriez", +"quatre-heurions", +"quatre-heurons", +"quatre-heurâmes", +"quatre-heurât", +"quatre-heurâtes", +"quatre-heurèrent", +"quatre-heuré", +"quatre-huit", +"quatre-mâts", +"quatre-pieds", +"quatre-quart", +"quatre-quarts", +"quatre-quatre", +"quatre-quatre-deux", +"quatre-quint", +"quatre-quints", +"quatre-quinze", +"quatre-quinzes", +"quatre-routois", +"quatre-routoise", +"quatre-routoises", +"quatre-saisons", +"quatre-temps", +"quatre-trois-trois", +"quatre-vingt", +"quatre-vingt-cinq", +"quatre-vingt-deux", +"quatre-vingt-dix", +"quatre-vingt-dix-huit", +"quatre-vingt-dix-neuf", +"quatre-vingt-dix-neuvième", +"quatre-vingt-dix-neuvièmes", +"quatre-vingt-dix-sept", +"quatre-vingt-dixième", +"quatre-vingt-dixièmes", +"quatre-vingt-dizaine", +"quatre-vingt-dizaines", +"quatre-vingt-douze", +"quatre-vingt-huit", +"quatre-vingt-neuf", +"quatre-vingt-onze", +"quatre-vingt-quatorze", +"quatre-vingt-quatre", +"quatre-vingt-quinze", +"quatre-vingt-seize", +"quatre-vingt-sept", +"quatre-vingt-six", +"quatre-vingt-treize", +"quatre-vingt-trois", +"quatre-vingt-un", +"quatre-vingt-une", +"quatre-vingtaine", +"quatre-vingtaines", +"quatre-vingtième", +"quatre-vingtièmes", +"quatre-vingts", +"quatre-épices", +"quatre-épées", +"quatre-œil", +"quatres-de-chiffre", +"que'ques", +"quelqu'un", +"quelqu'une", +"quelques-unes", +"quelques-uns", +"questche-wasser", +"question-piège", +"question-tag", +"questions-pièges", +"questions-réponses", +"questions-tags", +"queue-d'aronde", +"queue-d'hironde", +"queue-d'oison", +"queue-d'or", +"queue-de-carpe", +"queue-de-chat", +"queue-de-cheval", +"queue-de-cochon", +"queue-de-lion", +"queue-de-loup", +"queue-de-morue", +"queue-de-paon", +"queue-de-pie", +"queue-de-poireau", +"queue-de-porc", +"queue-de-pourceau", +"queue-de-poêle", +"queue-de-rat", +"queue-de-renard", +"queue-de-scorpion", +"queue-de-souris", +"queue-de-vache", +"queue-du-chat", +"queue-fourchue", +"queue-rouge", +"queues-d'aronde", +"queues-d'hironde", +"queues-d'or", +"queues-de-chat", +"queues-de-cheval", +"queues-de-cochon", +"queues-de-morue", +"queues-de-pie", +"queues-de-pourceau", +"queues-de-poêle", +"queues-de-rat", +"queues-de-renard", +"queues-de-vache", +"qui-va-là", +"qui-vive", +"quick-and-dirty", +"quintuple-croche", +"quintuples-croches", +"quinze-vingt", +"quinze-vingts", +"quizalofop-P-éthyl", +"quizalofop-p-éthyl", +"quizalofop-éthyl", +"quote-part", +"quotes-parts", +"r'endormaient", +"r'endormais", +"r'endormait", +"r'endormant", +"r'endorme", +"r'endorment", +"r'endormes", +"r'endormez", +"r'endormi", +"r'endormie", +"r'endormies", +"r'endormiez", +"r'endormions", +"r'endormir", +"r'endormira", +"r'endormirai", +"r'endormiraient", +"r'endormirais", +"r'endormirait", +"r'endormiras", +"r'endormirent", +"r'endormirez", +"r'endormiriez", +"r'endormirions", +"r'endormirons", +"r'endormiront", +"r'endormis", +"r'endormisse", +"r'endormissent", +"r'endormisses", +"r'endormissiez", +"r'endormissions", +"r'endormit", +"r'endormons", +"r'endormîmes", +"r'endormît", +"r'endormîtes", +"r'endors", +"r'endort", +"r'es", +"r'est", +"r'ouvert", +"r'ouverte", +"r'ouvertes", +"r'ouverts", +"r'ouvraient", +"r'ouvrais", +"r'ouvrait", +"r'ouvrant", +"r'ouvre", +"r'ouvrent", +"r'ouvres", +"r'ouvrez", +"r'ouvriez", +"r'ouvrions", +"r'ouvrir", +"r'ouvrira", +"r'ouvrirai", +"r'ouvriraient", +"r'ouvrirais", +"r'ouvrirait", +"r'ouvriras", +"r'ouvrirent", +"r'ouvrirez", +"r'ouvririez", +"r'ouvririons", +"r'ouvrirons", +"r'ouvriront", +"r'ouvris", +"r'ouvrisse", +"r'ouvrissent", +"r'ouvrisses", +"r'ouvrissiez", +"r'ouvrissions", +"r'ouvrit", +"r'ouvrons", +"r'ouvrîmes", +"r'ouvrît", +"r'ouvrîtes", +"r'étaient", +"r'étais", +"r'était", +"r'étant", +"r'étiez", +"r'étions", +"r'été", +"r'êtes", +"r'être", +"rabat-eau", +"rabat-eaux", +"rabat-joie", +"rabat-joies", +"rabi'-oul-aououal", +"rabi'-out-tani", +"racine-blanche", +"racines-blanches", +"rad'soc", +"rad'socs", +"rad-soc", +"rad-socs", +"radar-tronçon", +"radars-tronçons", +"radical-socialisme", +"radical-socialismes", +"radical-socialiste", +"radicale-socialiste", +"radicales-socialistes", +"radicaux-socialistes", +"radio-actinium", +"radio-activité", +"radio-activités", +"radio-amateur", +"radio-amateurs", +"radio-canadien", +"radio-carpien", +"radio-carpienne", +"radio-carpiennes", +"radio-carpiens", +"radio-crochet", +"radio-crochets", +"radio-cubital", +"radio-diffusion", +"radio-gramophone", +"radio-gramophones", +"radio-identification", +"radio-identifications", +"radio-interféromètre", +"radio-interféromètres", +"radio-isotope", +"radio-isotopes", +"radio-opacité", +"radio-opacités", +"radio-palmaire", +"radio-phonographe", +"radio-phonographes", +"radio-réalité", +"radio-réalités", +"radio-réveil", +"radio-taxi", +"radio-thorium", +"radio-télévision", +"radio-télévisions", +"radio-télévisé", +"radio-télévisée", +"radio-télévisées", +"radio-télévisés", +"radio-étiquette", +"radio-étiquettes", +"rag-time", +"rag-times", +"rahat-lokoum", +"rahat-lokoums", +"rahat-loukoum", +"rahat-loukoums", +"rai-de-coeur", +"rai-de-cœur", +"raid-aventure", +"raie-aigle", +"raie-guitare", +"raie-papillon", +"raies-aigles", +"raies-papillons", +"rail-road", +"rail-route", +"rais-de-coeur", +"rais-de-cœur", +"raisin-de-chien", +"raisins-de-chien", +"rallie-papier", +"rallonge-bouton", +"rallonge-boutons", +"ralé-poussé", +"ramasse-bourrier", +"ramasse-bourriers", +"ramasse-couvert", +"ramasse-couverts", +"ramasse-miette", +"ramasse-miettes", +"ramasse-monnaie", +"ramasse-poussière", +"ramasse-poussières", +"ramasse-ton-bras", +"ramasseuse-presse", +"ramasseuses-presses", +"ras-de-cou", +"ras-la-moule", +"ras-le-bol", +"ras-le-bonbon", +"ras-le-cresson", +"ras-les-fesses", +"rase-motte", +"rase-mottes", +"rase-pet", +"rase-pets", +"rat-baillet", +"rat-bayard", +"rat-de-cave", +"rat-garou", +"rat-taupe", +"rat-trompette", +"ratisse-caisse", +"rats-de-cave", +"rats-garous", +"ray-grass", +"raz-de-marée", +"re'em", +"re'ems", +"ready-made", +"reality-show", +"reality-shows", +"rebrousse-poil", +"recourbe-cils", +"recto-vaginal", +"recto-verso", +"redouble-cliqua", +"redouble-cliquai", +"redouble-cliquaient", +"redouble-cliquais", +"redouble-cliquait", +"redouble-cliquant", +"redouble-cliquas", +"redouble-cliquasse", +"redouble-cliquassent", +"redouble-cliquasses", +"redouble-cliquassiez", +"redouble-cliquassions", +"redouble-clique", +"redouble-cliquent", +"redouble-cliquer", +"redouble-cliquera", +"redouble-cliquerai", +"redouble-cliqueraient", +"redouble-cliquerais", +"redouble-cliquerait", +"redouble-cliqueras", +"redouble-cliquerez", +"redouble-cliqueriez", +"redouble-cliquerions", +"redouble-cliquerons", +"redouble-cliqueront", +"redouble-cliques", +"redouble-cliquez", +"redouble-cliquiez", +"redouble-cliquions", +"redouble-cliquons", +"redouble-cliquâmes", +"redouble-cliquât", +"redouble-cliquâtes", +"redouble-cliquèrent", +"redouble-cliqué", +"redresse-seins", +"refox-trotta", +"refox-trottai", +"refox-trottaient", +"refox-trottais", +"refox-trottait", +"refox-trottant", +"refox-trottas", +"refox-trottasse", +"refox-trottassent", +"refox-trottasses", +"refox-trottassiez", +"refox-trottassions", +"refox-trotte", +"refox-trottent", +"refox-trotter", +"refox-trottera", +"refox-trotterai", +"refox-trotteraient", +"refox-trotterais", +"refox-trotterait", +"refox-trotteras", +"refox-trotterez", +"refox-trotteriez", +"refox-trotterions", +"refox-trotterons", +"refox-trotteront", +"refox-trottes", +"refox-trottez", +"refox-trottiez", +"refox-trottions", +"refox-trottons", +"refox-trottâmes", +"refox-trottât", +"refox-trottâtes", +"refox-trottèrent", +"refox-trotté", +"regardez-moi", +"reine-claude", +"reine-des-bois", +"reine-des-prés", +"reine-marguerite", +"reines-claudes", +"reines-des-bois", +"reines-des-prés", +"reines-marguerites", +"relève-gravure", +"relève-gravures", +"relève-moustache", +"relève-moustaches", +"relève-quartier", +"relève-quartiers", +"relève-selle", +"relève-selles", +"remettez-vous", +"remicro-onda", +"remicro-ondai", +"remicro-ondaient", +"remicro-ondais", +"remicro-ondait", +"remicro-ondant", +"remicro-ondas", +"remicro-ondasse", +"remicro-ondassent", +"remicro-ondasses", +"remicro-ondassiez", +"remicro-ondassions", +"remicro-onde", +"remicro-ondent", +"remicro-onder", +"remicro-ondera", +"remicro-onderai", +"remicro-onderaient", +"remicro-onderais", +"remicro-onderait", +"remicro-onderas", +"remicro-onderez", +"remicro-onderiez", +"remicro-onderions", +"remicro-onderons", +"remicro-onderont", +"remicro-ondes", +"remicro-ondez", +"remicro-ondiez", +"remicro-ondions", +"remicro-ondons", +"remicro-ondâmes", +"remicro-ondât", +"remicro-ondâtes", +"remicro-ondèrent", +"remicro-ondé", +"remicro-ondée", +"remicro-ondées", +"remicro-ondés", +"remilly-wirquinois", +"remilly-wirquinoise", +"remilly-wirquinoises", +"remonte-pente", +"remonte-pentes", +"remue-ménage", +"remue-ménages", +"remue-méninge", +"remue-méninges", +"remue-queue", +"remue-queues", +"renard-garou", +"renarde-garou", +"rendez-vous", +"rennes-robots", +"renouée-bambou", +"rentr'ouvert", +"rentr'ouverte", +"rentr'ouvertes", +"rentr'ouverts", +"rentr'ouvraient", +"rentr'ouvrais", +"rentr'ouvrait", +"rentr'ouvrant", +"rentr'ouvre", +"rentr'ouvrent", +"rentr'ouvres", +"rentr'ouvrez", +"rentr'ouvriez", +"rentr'ouvrions", +"rentr'ouvrir", +"rentr'ouvrira", +"rentr'ouvrirai", +"rentr'ouvriraient", +"rentr'ouvrirais", +"rentr'ouvrirait", +"rentr'ouvriras", +"rentr'ouvrirent", +"rentr'ouvrirez", +"rentr'ouvririez", +"rentr'ouvririons", +"rentr'ouvrirons", +"rentr'ouvriront", +"rentr'ouvris", +"rentr'ouvrisse", +"rentr'ouvrissent", +"rentr'ouvrisses", +"rentr'ouvrissiez", +"rentr'ouvrissions", +"rentr'ouvrit", +"rentr'ouvrons", +"rentr'ouvrîmes", +"rentr'ouvrît", +"rentr'ouvrîtes", +"rentre-dedans", +"renvoi-instruire", +"repetit-déjeuna", +"repetit-déjeunai", +"repetit-déjeunaient", +"repetit-déjeunais", +"repetit-déjeunait", +"repetit-déjeunant", +"repetit-déjeunas", +"repetit-déjeunasse", +"repetit-déjeunassent", +"repetit-déjeunasses", +"repetit-déjeunassiez", +"repetit-déjeunassions", +"repetit-déjeune", +"repetit-déjeunent", +"repetit-déjeuner", +"repetit-déjeunera", +"repetit-déjeunerai", +"repetit-déjeuneraient", +"repetit-déjeunerais", +"repetit-déjeunerait", +"repetit-déjeuneras", +"repetit-déjeunerez", +"repetit-déjeuneriez", +"repetit-déjeunerions", +"repetit-déjeunerons", +"repetit-déjeuneront", +"repetit-déjeunes", +"repetit-déjeunez", +"repetit-déjeuniez", +"repetit-déjeunions", +"repetit-déjeunons", +"repetit-déjeunâmes", +"repetit-déjeunât", +"repetit-déjeunâtes", +"repetit-déjeunèrent", +"repetit-déjeuné", +"repique-niqua", +"repique-niquai", +"repique-niquaient", +"repique-niquais", +"repique-niquait", +"repique-niquant", +"repique-niquas", +"repique-niquasse", +"repique-niquassent", +"repique-niquasses", +"repique-niquassiez", +"repique-niquassions", +"repique-nique", +"repique-niquent", +"repique-niquer", +"repique-niquera", +"repique-niquerai", +"repique-niqueraient", +"repique-niquerais", +"repique-niquerait", +"repique-niqueras", +"repique-niquerez", +"repique-niqueriez", +"repique-niquerions", +"repique-niquerons", +"repique-niqueront", +"repique-niques", +"repique-niquez", +"repique-niquiez", +"repique-niquions", +"repique-niquons", +"repique-niquâmes", +"repique-niquât", +"repique-niquâtes", +"repique-niquèrent", +"repique-niqué", +"repose-pied", +"repose-pieds", +"repose-poignet", +"repose-poignets", +"repose-tête", +"repose-têtes", +"requin-baleine", +"requin-chabot", +"requin-chat", +"requin-chats", +"requin-citron", +"requin-corail", +"requin-crocodile", +"requin-garou", +"requin-griset", +"requin-hâ", +"requin-maquereau", +"requin-marteau", +"requin-nourrice", +"requin-renard", +"requin-taupe", +"requin-taureau", +"requin-tigre", +"requin-vache", +"requin-zèbre", +"requins-baleines", +"requins-citrons", +"requins-crocodiles", +"requins-garous", +"requins-hâ", +"requins-marteaux", +"requins-taupes", +"requins-tigres", +"rest-o-pack", +"restaurant-bar", +"restaurant-bistro", +"restaurant-brasserie", +"restaurant-pub", +"restaurants-bistros", +"reste-avec", +"resto-bar", +"resto-bistro", +"resto-brasserie", +"resto-pub", +"retraite-chapeau", +"retraites-chapeaux", +"retroussons-nos-manches", +"revenant-bon", +"revenants-bons", +"revenez-y", +"rex-castor", +"rex-castors", +"rez-de-chaussée", +"rez-de-cour", +"rez-de-jardin", +"rez-mur", +"rhodesian-ridgeback", +"rhéo-fluidifiant", +"rhéo-fluidifiante", +"rhéo-fluidifiantes", +"rhéo-fluidifiants", +"rhéo-épaississant", +"rhéo-épaississante", +"rhéo-épaississantes", +"rhéo-épaississants", +"rhéto-roman", +"rhéto-romane", +"rhéto-romanes", +"rhéto-romans", +"ria-sirachois", +"ria-sirachoise", +"ria-sirachoises", +"ric-rac", +"ric-à-rac", +"rick-rolla", +"rick-rollai", +"rick-rollaient", +"rick-rollais", +"rick-rollait", +"rick-rollant", +"rick-rollas", +"rick-rollasse", +"rick-rollassent", +"rick-rollasses", +"rick-rollassiez", +"rick-rollassions", +"rick-rolle", +"rick-rollent", +"rick-roller", +"rick-rollera", +"rick-rollerai", +"rick-rolleraient", +"rick-rollerais", +"rick-rollerait", +"rick-rolleras", +"rick-rollerez", +"rick-rolleriez", +"rick-rollerions", +"rick-rollerons", +"rick-rolleront", +"rick-rolles", +"rick-rollez", +"rick-rolliez", +"rick-rollions", +"rick-rollons", +"rick-rollâmes", +"rick-rollât", +"rick-rollâtes", +"rick-rollèrent", +"rick-rollé", +"rick-rollée", +"rick-rollées", +"rick-rollés", +"rieux-en-valois", +"rieux-en-valoise", +"rieux-en-valoises", +"rigaud-montain", +"rigaud-montaine", +"rigaud-montaines", +"rigaud-montains", +"rigny-usséen", +"rigny-usséenne", +"rigny-usséennes", +"rigny-usséens", +"rince-bouche", +"rince-bouches", +"rince-bouteille", +"rince-bouteilles", +"rince-doigt", +"rince-doigts", +"risque-tout", +"riz-pain-sel", +"road-book", +"road-books", +"roast-beef", +"roast-beefs", +"robe-chandail", +"robe-housse", +"robert-le-diable", +"robert-messin", +"robert-messine", +"robert-messines", +"robert-messins", +"robes-chandails", +"robes-housses", +"robot-chien", +"robots-chiens", +"roche-blanchais", +"roche-blanchaise", +"roche-blanchaises", +"roche-mère", +"roche-papier-ciseaux", +"roches-mères", +"rock'n'roll", +"rock-a-billy", +"rocking-chair", +"rocking-chairs", +"roge-bougeron", +"roge-bougeronne", +"roge-bougeronnes", +"roge-bougerons", +"roger-bontemps", +"rogne-cul", +"rogne-pied", +"rogne-pieds", +"rogne-salaires", +"roi-de-rats", +"rois-de-rats", +"roll-out", +"roll-outs", +"roller-derby", +"roller-derbys", +"roman-feuilleton", +"roman-fleuve", +"roman-photo", +"roman-photos", +"romans-feuilletons", +"romans-fleuves", +"romans-photos", +"rompt-pierre", +"rompt-pierres", +"ron-ron", +"rond-de-cuir", +"rond-point", +"rond-ponna", +"rond-ponnai", +"rond-ponnaient", +"rond-ponnais", +"rond-ponnait", +"rond-ponnant", +"rond-ponnas", +"rond-ponnasse", +"rond-ponnassent", +"rond-ponnasses", +"rond-ponnassiez", +"rond-ponnassions", +"rond-ponne", +"rond-ponnent", +"rond-ponner", +"rond-ponnera", +"rond-ponnerai", +"rond-ponneraient", +"rond-ponnerais", +"rond-ponnerait", +"rond-ponneras", +"rond-ponnerez", +"rond-ponneriez", +"rond-ponnerions", +"rond-ponnerons", +"rond-ponneront", +"rond-ponnes", +"rond-ponnez", +"rond-ponniez", +"rond-ponnions", +"rond-ponnons", +"rond-ponnâmes", +"rond-ponnât", +"rond-ponnâtes", +"rond-ponnèrent", +"rond-ponné", +"ronde-bosse", +"ronde-bosses", +"rondes-bosses", +"ronds-de-cuir", +"ronds-points", +"ronge-bois", +"ronge-maille", +"rongo-rongo", +"roost-warendinois", +"roost-warendinoise", +"roost-warendinoises", +"rose-croix", +"rose-de-mer", +"rose-marine", +"roses-marines", +"rosti-montois", +"rosti-montoise", +"rosti-montoises", +"rouge-aile", +"rouge-bord", +"rouge-brun", +"rouge-flasher", +"rouge-gorge", +"rouge-herbe", +"rouge-herbes", +"rouge-noir", +"rouge-pie", +"rouge-queue", +"rouges-ailes", +"rouges-gorges", +"rouges-queues", +"rouget-barbet", +"rouget-grondin", +"roul-sa-bosse", +"roulage-décollage", +"roule-goupille", +"roule-goupilles", +"roule-ta-bosse", +"rouler-bouler", +"roullet-stéphanois", +"roullet-stéphanoise", +"roullet-stéphanoises", +"roulé-boulé", +"roulé-saucisse", +"roulés-boulés", +"rousse-tête", +"rousses-têtes", +"roux-mirien", +"rufino-sulfurique", +"rufino-sulfuriques", +"ruine-babine", +"ruine-babines", +"russo-allemand", +"russo-allemande", +"russo-allemandes", +"russo-allemands", +"russo-américain", +"russo-japonaise", +"russo-polonaise", +"râlé-poussé", +"réal-politique", +"réal-politiques", +"réarc-bouta", +"réarc-boutai", +"réarc-boutaient", +"réarc-boutais", +"réarc-boutait", +"réarc-boutant", +"réarc-boutas", +"réarc-boutasse", +"réarc-boutassent", +"réarc-boutasses", +"réarc-boutassiez", +"réarc-boutassions", +"réarc-boute", +"réarc-boutent", +"réarc-bouter", +"réarc-boutera", +"réarc-bouterai", +"réarc-bouteraient", +"réarc-bouterais", +"réarc-bouterait", +"réarc-bouteras", +"réarc-bouterez", +"réarc-bouteriez", +"réarc-bouterions", +"réarc-bouterons", +"réarc-bouteront", +"réarc-boutes", +"réarc-boutez", +"réarc-boutiez", +"réarc-boutions", +"réarc-boutons", +"réarc-boutâmes", +"réarc-boutât", +"réarc-boutâtes", +"réarc-boutèrent", +"réarc-bouté", +"réarc-boutée", +"réarc-boutées", +"réarc-boutés", +"réception-cadeaux", +"récipient-mesure", +"récipient-mesures", +"réentr'apercevaient", +"réentr'apercevais", +"réentr'apercevait", +"réentr'apercevant", +"réentr'apercevez", +"réentr'aperceviez", +"réentr'apercevions", +"réentr'apercevoir", +"réentr'apercevons", +"réentr'apercevra", +"réentr'apercevrai", +"réentr'apercevraient", +"réentr'apercevrais", +"réentr'apercevrait", +"réentr'apercevras", +"réentr'apercevrez", +"réentr'apercevriez", +"réentr'apercevrions", +"réentr'apercevrons", +"réentr'apercevront", +"réentr'aperçois", +"réentr'aperçoit", +"réentr'aperçoive", +"réentr'aperçoivent", +"réentr'aperçoives", +"réentr'aperçu", +"réentr'aperçue", +"réentr'aperçues", +"réentr'aperçurent", +"réentr'aperçus", +"réentr'aperçusse", +"réentr'aperçussent", +"réentr'aperçusses", +"réentr'aperçussiez", +"réentr'aperçussions", +"réentr'aperçut", +"réentr'aperçûmes", +"réentr'aperçût", +"réentr'aperçûtes", +"réentr'ouvert", +"réentr'ouverte", +"réentr'ouvertes", +"réentr'ouverts", +"réentr'ouvraient", +"réentr'ouvrais", +"réentr'ouvrait", +"réentr'ouvrant", +"réentr'ouvre", +"réentr'ouvrent", +"réentr'ouvres", +"réentr'ouvrez", +"réentr'ouvriez", +"réentr'ouvrions", +"réentr'ouvrir", +"réentr'ouvrira", +"réentr'ouvrirai", +"réentr'ouvriraient", +"réentr'ouvrirais", +"réentr'ouvrirait", +"réentr'ouvriras", +"réentr'ouvrirent", +"réentr'ouvrirez", +"réentr'ouvririez", +"réentr'ouvririons", +"réentr'ouvrirons", +"réentr'ouvriront", +"réentr'ouvris", +"réentr'ouvrisse", +"réentr'ouvrissent", +"réentr'ouvrisses", +"réentr'ouvrissiez", +"réentr'ouvrissions", +"réentr'ouvrit", +"réentr'ouvrons", +"réentr'ouvrîmes", +"réentr'ouvrît", +"réentr'ouvrîtes", +"régis-borgien", +"régis-borgienne", +"régis-borgiennes", +"régis-borgiens", +"rémy-montais", +"rémy-montaise", +"rémy-montaises", +"répondeur-enregistreur", +"répondeur-enregistreurs", +"résino-gommeux", +"réunion-bilan", +"réunions-bilan", +"réveil-matin", +"réveille-matin", +"réveille-matins", +"rêve-creux", +"rü'üsá", +"sa'ban", +"sabre-peuple", +"sac-jacking", +"sac-poubelle", +"saccharo-glycose", +"sacro-iliaques", +"sacro-lombaire", +"sacro-saint", +"sacro-sainte", +"sacro-saintement", +"sacro-saintes", +"sacro-saints", +"sacro-vertébral", +"sacré-coeur", +"sacré-cœur", +"sacs-poubelle", +"sacs-poubelles", +"sado-maso", +"sado-masochisme", +"sado-masochiste", +"sado-masochistes", +"safari-parc", +"safari-parcs", +"sage-femme", +"sage-homme", +"sages-femmes", +"sahélo-saharien", +"sahélo-saharienne", +"sahélo-sahariennes", +"sahélo-sahariens", +"saigne-nez", +"sain-belois", +"sain-beloise", +"sain-beloises", +"sain-bois", +"sain-foin", +"saisie-arrêt", +"saisie-attribution", +"saisie-brandon", +"saisie-exécution", +"saisie-gagerie", +"saisie-revendication", +"saisies-arrêts", +"saisies-attributions", +"saisies-brandons", +"saisies-exécutions", +"saisies-gageries", +"saisies-revendications", +"saisir-arrêter", +"saisir-brandonner", +"saisir-exécuter", +"saisir-gager", +"saisir-revendiquer", +"salafo-sioniste", +"salaire-coût", +"salaire-coûts", +"salamandre-tigre", +"salle-prunetais", +"salle-prunetaise", +"salle-prunetaises", +"salles-sourçois", +"salles-sourçoise", +"salles-sourçoises", +"salpingo-pharyngien", +"salve-d'honneur", +"salves-d'honneur", +"sam'suffit", +"sam'suffits", +"san-benito", +"san-bérinois", +"san-bérinoise", +"san-bérinoises", +"san-claudien", +"san-damianais", +"san-damianaise", +"san-damianaises", +"san-denien", +"san-denienne", +"san-deniennes", +"san-deniens", +"san-desiderois", +"san-desideroise", +"san-desideroises", +"san-farcios", +"san-farciose", +"san-farcioses", +"san-ferrois", +"san-ferroise", +"san-ferroises", +"san-genestois", +"san-genestoise", +"san-genestoises", +"san-germinois", +"san-germinoise", +"san-germinoises", +"san-lagiron", +"san-lagirone", +"san-lagirones", +"san-lagirons", +"san-martinois", +"san-martinoise", +"san-martinoises", +"san-miardère", +"san-miardères", +"san-palous", +"san-palouse", +"san-palouses", +"san-pierran", +"san-pierrane", +"san-pierranes", +"san-pierrans", +"san-priot", +"san-priote", +"san-priotes", +"san-priots", +"san-pétri-montin", +"san-pétri-montine", +"san-pétri-montines", +"san-pétri-montins", +"san-rémois", +"san-rémoise", +"san-rémoises", +"san-salvatorien", +"san-salvatorienne", +"san-salvatoriennes", +"san-salvatoriens", +"san-vitournaire", +"san-vitournaires", +"sancto-bénédictin", +"sancto-bénédictine", +"sancto-bénédictines", +"sancto-bénédictins", +"sancto-julianais", +"sancto-julianaise", +"sancto-julianaises", +"sancto-prixin", +"sancto-prixine", +"sancto-prixines", +"sancto-prixins", +"sang-de-bourbe", +"sang-de-dragon", +"sang-froid", +"sang-gris", +"sang-mêlé", +"sang-mêlés", +"sankaku-jime", +"santi-johanien", +"santi-johanienne", +"santi-johaniennes", +"santi-johaniens", +"santoline-cyprès", +"sapeur-pompier", +"sapeurs-pompiers", +"sapeuse-pompière", +"sapeuses-pompières", +"sarclo-buttage", +"sarclo-buttages", +"sarco-hydrocèle", +"sarco-hydrocèles", +"sarco-épiplocèle", +"sarco-épiplomphale", +"sarco-épiplomphales", +"sarre-unionnais", +"sarre-unionnaise", +"sarre-unionnaises", +"sart-dames-avelinois", +"sart-eustachois", +"sart-risbartois", +"satellites-espions", +"sati-drap", +"sauf-conduit", +"sauf-conduits", +"saugnac-et-muretois", +"saugnac-et-muretoise", +"saugnac-et-muretoises", +"sault-rethelois", +"sault-retheloise", +"sault-retheloises", +"saut-de-lit", +"saut-de-lits", +"saut-de-loup", +"saut-de-mouton", +"saute-au-paf", +"saute-bouchon", +"saute-bouchons", +"saute-en-barque", +"saute-en-bas", +"saute-mouton", +"saute-moutons", +"saute-ruisseau", +"saute-ruisseaux", +"sauts-de-lit", +"sauts-de-mouton", +"sauve-l'honneur", +"sauve-qui-peut", +"sauve-rabans", +"sauve-vie", +"savez-vous", +"savoir-faire", +"savoir-vivre", +"scale-out", +"scale-up", +"scaphoïdo-astragalien", +"scaphoïdo-cuboïdien", +"sceau-cylindre", +"sceau-de-Notre-Dame", +"sceau-de-salomon", +"sceaux-cylindres", +"sceaux-de-Notre-Dame", +"schiste-carton", +"schistes-carton", +"scie-cloche", +"science-fictif", +"science-fiction", +"science-fictions", +"sciences-fiction", +"sciences-fictions", +"scies-cloches", +"scirpo-phragmitaie", +"scirpo-phragmitaies", +"scottish-terrier", +"scuto-sternal", +"scènes-clés", +"seconde-lumière", +"secondes-lumière", +"seine-et-marnais", +"seine-et-marnaise", +"seine-et-marnaises", +"seine-portais", +"seine-portaise", +"seine-portaises", +"self-control", +"self-défense", +"self-government", +"self-governments", +"self-made-man", +"self-made-mans", +"self-made-men", +"self-made-woman", +"self-made-womans", +"self-made-women", +"self-service", +"self-services", +"selk'nam", +"selles-sur-cher", +"semaine-lumière", +"semaines-lumière", +"semen-contra", +"semper-virens", +"sensori-moteur", +"sensori-moteurs", +"sensori-motrice", +"sensori-motrices", +"sensori-motricité", +"sent-bon", +"sept-en-gueule", +"sept-en-huit", +"sept-et-le-va", +"sept-frèrien", +"sept-frèrienne", +"sept-frèriennes", +"sept-frèriens", +"sept-meulois", +"sept-meuloise", +"sept-meuloises", +"sept-mâts", +"sept-oeil", +"sept-oeils", +"sept-sortais", +"sept-sortaise", +"sept-sortaises", +"sept-ventais", +"sept-ventaise", +"sept-ventaises", +"sept-œil", +"sept-œils", +"septante-cinq", +"septante-deux", +"septante-et-un", +"septante-huit", +"septante-neuf", +"septante-quatre", +"septante-sept", +"septante-six", +"septante-trois", +"septentrio-occidental", +"septentrio-occidentale", +"septentrio-occidentales", +"septentrio-occidentaux", +"serbo-croate", +"sergent-chef", +"sergent-major", +"sergents-chefs", +"sergents-majors", +"serre-bauquière", +"serre-bosse", +"serre-bosses", +"serre-bras", +"serre-ciseau", +"serre-ciseaux", +"serre-cou", +"serre-cous", +"serre-feu", +"serre-feux", +"serre-fil", +"serre-file", +"serre-files", +"serre-fils", +"serre-fine", +"serre-frein", +"serre-joint", +"serre-joints", +"serre-livre", +"serre-livres", +"serre-malice", +"serre-nerpolain", +"serre-nerpolaine", +"serre-nerpolaines", +"serre-nerpolains", +"serre-nez", +"serre-noeud", +"serre-nœud", +"serre-nœuds", +"serre-papier", +"serre-papiers", +"serre-point", +"serre-points", +"serre-pédicule", +"serre-pédicules", +"serre-rails", +"serre-taille", +"serre-tailles", +"serre-tube", +"serre-tubes", +"serre-tête", +"serre-têtes", +"serres-fines", +"serres-gastonnais", +"serres-gastonnaise", +"serres-gastonnaises", +"serres-morlanais", +"serres-morlanaise", +"serres-morlanaises", +"serri-sapinois", +"serri-sapinoise", +"serri-sapinoises", +"service-volée", +"services-volées", +"serviette-éponge", +"serviettes-éponges", +"servo-direction", +"servo-directions", +"servo-frein", +"servo-freins", +"servo-moteur", +"seul-en-scène", +"seule-en-scène", +"sex-appeal", +"sex-digital", +"sex-digitisme", +"sex-digitismes", +"sex-ratio", +"sex-ratios", +"sex-shop", +"sex-shops", +"sex-symbol", +"sex-symbols", +"sex-toy", +"sex-toys", +"sexe-ratio", +"shabu-shabu", +"shar-peï", +"shar-peïs", +"shift-cliqua", +"shift-cliquai", +"shift-cliquaient", +"shift-cliquais", +"shift-cliquait", +"shift-cliquant", +"shift-cliquas", +"shift-cliquasse", +"shift-cliquassent", +"shift-cliquasses", +"shift-cliquassiez", +"shift-cliquassions", +"shift-clique", +"shift-cliquent", +"shift-cliquer", +"shift-cliquera", +"shift-cliquerai", +"shift-cliqueraient", +"shift-cliquerais", +"shift-cliquerait", +"shift-cliqueras", +"shift-cliquerez", +"shift-cliqueriez", +"shift-cliquerions", +"shift-cliquerons", +"shift-cliqueront", +"shift-cliques", +"shift-cliquez", +"shift-cliquiez", +"shift-cliquions", +"shift-cliquons", +"shift-cliquâmes", +"shift-cliquât", +"shift-cliquâtes", +"shift-cliquèrent", +"shift-cliqué", +"shift-cliquée", +"shift-cliquées", +"shift-cliqués", +"shikoku-inu", +"shipibo-conibo", +"shoot-'em-up", +"short-culotte", +"short-culottes", +"short-track", +"short-tracks", +"show-biz", +"show-business", +"sicilio-sarde", +"side-car", +"side-cariste", +"side-caristes", +"side-cars", +"sierra-léonais", +"sierra-léonaise", +"sierra-léonaises", +"sigma-additif", +"sigma-additivité", +"sigma-additivités", +"silicico-aluminique", +"silicico-aluminiques", +"silicico-cuivreux", +"silure-spatule", +"simili-cuir", +"simili-cuirs", +"singe-araignée", +"singe-chouette", +"singe-lion", +"singe-écureuil", +"singes-araignées", +"singes-chouettes", +"singes-lions", +"singes-écureuils", +"sino-américain", +"sino-américaine", +"sino-américaines", +"sino-américains", +"sino-australien", +"sino-australienne", +"sino-australiennes", +"sino-australiens", +"sino-canadien", +"sino-colombien", +"sino-colombienne", +"sino-colombiennes", +"sino-colombiens", +"sino-congolais", +"sino-continental", +"sino-coréen", +"sino-européen", +"sino-japonais", +"sino-japonaise", +"sino-japonaises", +"sino-québécois", +"sino-taïwanais", +"sino-tibétain", +"sino-vietnamien", +"sino-vietnamienne", +"sino-vietnamiennes", +"sino-vietnamiens", +"sino-égyptien", +"sino-égyptienne", +"sino-égyptiennes", +"sino-égyptiens", +"sister-ship", +"sister-ships", +"sit-in", +"sit-ins", +"sit-up", +"sit-ups", +"six-cent-soixante-six", +"six-cent-soixante-sixième", +"six-cent-soixante-sixièmes", +"six-cents", +"six-clefs", +"six-coups", +"six-doigts", +"six-fournais", +"six-fournaise", +"six-fournaises", +"six-mâts", +"six-vingts", +"siècle-lumière", +"siècles-lumière", +"ski-alpinisme", +"ski-alpinismes", +"ski-alpiniste", +"ski-alpinistes", +"sleeping-car", +"sloop-of-war", +"slop-tank", +"smaragdo-chalcite", +"smaragdo-chalcites", +"snack-bar", +"snack-bars", +"snow-boot", +"snow-boots", +"soap-opéra", +"soaps-opéras", +"sociale-démocrate", +"sociale-traitre", +"sociale-traître", +"sociales-démocrates", +"sociales-traitres", +"sociales-traîtres", +"sociaux-démocrates", +"sociaux-traitres", +"sociaux-traîtres", +"socio-cible", +"socio-cibles", +"socio-culturel", +"socio-culturelle", +"socio-culturelles", +"socio-culturels", +"socio-esthéticien", +"socio-esthéticiens", +"socio-historiographe", +"socio-historiographes", +"socio-historique", +"socio-historiques", +"socio-politique", +"socio-politiques", +"socio-professionnel", +"socio-professionnelle", +"socio-professionnelles", +"socio-professionnels", +"socio-économique", +"socio-économiques", +"socio-éducatif", +"socio-éducatifs", +"socio-éducative", +"socio-éducatives", +"société-écran", +"sociétés-écrans", +"soda-spodumenes", +"sodo-calcique", +"sodo-calciques", +"soi-disamment", +"soi-disant", +"soi-même", +"soit-communiqué", +"soixante-cinq", +"soixante-deux", +"soixante-dix", +"soixante-dix-huit", +"soixante-dix-neuf", +"soixante-dix-sept", +"soixante-dixième", +"soixante-dixièmes", +"soixante-dizaine", +"soixante-dizaines", +"soixante-douze", +"soixante-et-onze", +"soixante-et-un", +"soixante-et-une", +"soixante-huit", +"soixante-huitard", +"soixante-huitarde", +"soixante-huitardes", +"soixante-huitards", +"soixante-neuf", +"soixante-quatorze", +"soixante-quatre", +"soixante-quinze", +"soixante-seize", +"soixante-sept", +"soixante-six", +"soixante-treize", +"soixante-trois", +"sole-ruardon", +"solliès-pontois", +"solliès-pontoise", +"solliès-pontoises", +"solliès-villain", +"solliès-villaine", +"solliès-villaines", +"solliès-villains", +"somato-psychique", +"somato-psychiques", +"somme-leuzien", +"somme-suippas", +"somme-suippase", +"somme-suippases", +"son-et-lumière", +"songe-creux", +"songe-malice", +"songhaï-zarma", +"songhaï-zarmas", +"sortie-de-bain", +"sortie-de-bal", +"sot-l'y-laisse", +"sotto-voce", +"sou-chong", +"sou-chongs", +"soudano-tchado-lybien", +"soudo-brasa", +"soudo-brasai", +"soudo-brasaient", +"soudo-brasais", +"soudo-brasait", +"soudo-brasant", +"soudo-brasas", +"soudo-brasasse", +"soudo-brasassent", +"soudo-brasasses", +"soudo-brasassiez", +"soudo-brasassions", +"soudo-brase", +"soudo-brasent", +"soudo-braser", +"soudo-brasera", +"soudo-braserai", +"soudo-braseraient", +"soudo-braserais", +"soudo-braserait", +"soudo-braseras", +"soudo-braserez", +"soudo-braseriez", +"soudo-braserions", +"soudo-braserons", +"soudo-braseront", +"soudo-brases", +"soudo-brasez", +"soudo-brasiez", +"soudo-brasions", +"soudo-brasons", +"soudo-brasâmes", +"soudo-brasât", +"soudo-brasâtes", +"soudo-brasèrent", +"soudo-brasé", +"soudo-brasée", +"soudo-brasées", +"soudo-brasés", +"souffre-douleur", +"souffre-douleurs", +"soufre-sélénifère", +"soum-soum", +"soupe-tout-seul", +"sourd-muet", +"sourd-parlant", +"sourde-muette", +"sourdes-muettes", +"sourds-muets", +"souris-chauve", +"souris-chauves", +"souris-crayon", +"souris-crayons", +"souris-opossums", +"souris-stylo", +"souris-stylos", +"soutien-gorge", +"soutien-loloches", +"soutiens-gorge", +"souvenez-vous-de-moi", +"souveraineté-association", +"souï-manga", +"soŋay-zarma", +"soŋay-zarmas", +"sparring-partner", +"spatio-temporel", +"spatio-temporelle", +"spatio-temporelles", +"spatio-temporels", +"speed-dating", +"sphinx-bourdon", +"sphéno-temporal", +"spin-off", +"spin-offs", +"spina-bifida", +"spina-ventosa", +"spiro-bloc", +"spiro-blocs", +"sport-étude", +"sportivo-financier", +"sports-études", +"spruce-beer", +"squale-grogneur", +"sri-lankais", +"sri-lankaise", +"sri-lankaises", +"st'at'imc", +"stabilo-bossa", +"stabilo-bossai", +"stabilo-bossaient", +"stabilo-bossais", +"stabilo-bossait", +"stabilo-bossant", +"stabilo-bossas", +"stabilo-bossasse", +"stabilo-bossassent", +"stabilo-bossasses", +"stabilo-bossassiez", +"stabilo-bossassions", +"stabilo-bosse", +"stabilo-bossent", +"stabilo-bosser", +"stabilo-bossera", +"stabilo-bosserai", +"stabilo-bosseraient", +"stabilo-bosserais", +"stabilo-bosserait", +"stabilo-bosseras", +"stabilo-bosserez", +"stabilo-bosseriez", +"stabilo-bosserions", +"stabilo-bosserons", +"stabilo-bosseront", +"stabilo-bosses", +"stabilo-bossez", +"stabilo-bossiez", +"stabilo-bossions", +"stabilo-bossons", +"stabilo-bossâmes", +"stabilo-bossât", +"stabilo-bossâtes", +"stabilo-bossèrent", +"stabilo-bossé", +"stabilo-bossée", +"stabilo-bossées", +"stabilo-bossés", +"stage-coach", +"stage-coachs", +"stand-by", +"stand-up", +"stannoso-potassique", +"star-système", +"star-systèmes", +"start-up", +"start-upeur", +"starting-block", +"starting-blocks", +"starting-gate", +"station-service", +"stations-service", +"stations-services", +"statue-menhir", +"statues-menhirs", +"steam-boat", +"steam-boats", +"steeple-chase", +"step-back", +"step-backs", +"sterno-claviculaire", +"sterno-claviculaires", +"sterno-clido-mastoïdien", +"sterno-clido-mastoïdienne", +"sterno-clido-mastoïdiennes", +"sterno-clido-mastoïdiens", +"sterno-cléido-mastoïdien", +"sterno-cléido-mastoïdiens", +"sterno-huméral", +"sterno-hyoïdien", +"sterno-pubien", +"stock-car", +"stock-cars", +"stock-option", +"stock-options", +"stock-tampon", +"stocks-tampons", +"stomo-gastrique", +"stomo-gastriques", +"stop-ski", +"stop-skis", +"story-board", +"story-boards", +"strauss-kahnien", +"strauss-kahniens", +"street-artiste", +"street-artistes", +"street-gadz", +"strip-teasa", +"strip-teasai", +"strip-teasaient", +"strip-teasais", +"strip-teasait", +"strip-teasant", +"strip-teasas", +"strip-teasasse", +"strip-teasassent", +"strip-teasasses", +"strip-teasassiez", +"strip-teasassions", +"strip-tease", +"strip-teasent", +"strip-teaser", +"strip-teasera", +"strip-teaserai", +"strip-teaseraient", +"strip-teaserais", +"strip-teaserait", +"strip-teaseras", +"strip-teaserez", +"strip-teaseriez", +"strip-teaserions", +"strip-teaserons", +"strip-teaseront", +"strip-teases", +"strip-teaseurs", +"strip-teaseuse", +"strip-teaseuses", +"strip-teasez", +"strip-teasiez", +"strip-teasions", +"strip-teasons", +"strip-teasâmes", +"strip-teasât", +"strip-teasâtes", +"strip-teasèrent", +"strip-teasé", +"strip-teasée", +"strip-teasées", +"strip-teasés", +"stroke-play", +"strom-apparat", +"struggle-for-life", +"struggle-for-lifes", +"stud-book", +"stuffing-box", +"stylo-bille", +"stylo-billes", +"stylo-feutre", +"stylo-glosse", +"stylo-gomme", +"stylo-pistolet", +"stylo-plume", +"stylo-souris", +"stylos-feutres", +"stylos-gommes", +"stylos-plume", +"stylos-souris", +"sténo-dactylographe", +"sténo-dactylographes", +"sténo-méditerranéen", +"sténo-méditerranéenne", +"sténo-méditerranéennes", +"sténo-méditerranéens", +"stéphano-carladésien", +"stéphano-carladésienne", +"stéphano-carladésiennes", +"stéphano-carladésiens", +"stéréo-isomère", +"stéréo-isomères", +"su-sucre", +"su-sucres", +"subrogé-tuteur", +"subrogés-tuteurs", +"suce-boules", +"suce-bœuf", +"suce-fleur", +"suce-fleurs", +"suce-goulot", +"suce-goulots", +"suce-médailles", +"sudoro-algique", +"suivez-moi-jeune-homme", +"sulfo-margarique", +"suméro-akkadien", +"suméro-akkadienne", +"suméro-akkadiennes", +"suméro-akkadiens", +"super-8", +"support-chaussettes", +"supports-chaussettes", +"supra-axillaire", +"supra-axillaires", +"supra-caudal", +"supra-caudale", +"supra-caudales", +"supra-caudaux", +"supra-épineux", +"surdi-mutité", +"surdi-mutités", +"suro-pédieuse", +"suro-pédieuses", +"suro-pédieux", +"surprise-partie", +"surprise-parties", +"surprises-parties", +"surveillant-général", +"sus-caudal", +"sus-cité", +"sus-coccygien", +"sus-dominante", +"sus-dominantes", +"sus-hyoïdien", +"sus-hépatique", +"sus-hépatiques", +"sus-jacent", +"sus-jacents", +"sus-maxillo-labial", +"sus-maxillo-nasal", +"sus-métatarsien", +"sus-métatarsienne", +"sus-métatarsiennes", +"sus-métatarsiens", +"sus-naseau", +"sus-naso-labial", +"sus-pied", +"sus-pubio-fémoral", +"sus-tarsien", +"sus-tarsienne", +"sus-tarsiennes", +"sus-tarsiens", +"sus-tentoriel", +"sus-tentorielle", +"sus-tentorielles", +"sus-tentoriels", +"sus-tonique", +"sus-épineux", +"suédo-américain", +"suédo-américaine", +"suédo-américaines", +"suédo-américains", +"sweat-shirt", +"sweat-shirts", +"syndesmo-pharyngien", +"syro-chaldaïque", +"syro-chaldéen", +"syro-chaldéens", +"syro-saoudien", +"systèmes-clés", +"sèche-cheveu", +"sèche-cheveux", +"sèche-linge", +"séchoir-atomiseur", +"séchoir-atomiseurs", +"sénateur-maire", +"sénatus-consulte", +"sénatus-consultes", +"séro-sanguin", +"séro-sanguine", +"séro-sanguines", +"séro-sanguins", +"t'inquiète", +"t'occupe", +"t'oh", +"t-bone", +"t-bones", +"t-elle", +"t-il", +"t-on", +"t-shirt", +"t-shirts", +"tabagn's", +"table-bureau", +"tables-bureaux", +"tac-tac", +"tai-kadai", +"taille-crayon", +"taille-crayons", +"taille-douce", +"taille-haie", +"taille-haies", +"taille-mer", +"taille-mers", +"taille-mèche", +"taille-mèches", +"taille-plume", +"taille-plumes", +"taille-pré", +"taille-prés", +"taille-vent", +"taille-vents", +"tailles-douces", +"taki-taki", +"talco-micacé", +"talco-quartzeux", +"talk-show", +"talkie-walkie", +"talkie-walkies", +"talkies-walkies", +"taly-pen", +"taly-pens", +"tam-tam", +"tam-tams", +"tambour-major", +"tambours-majors", +"tams-tams", +"tao-taï", +"tao-taïs", +"tape-beurre", +"tape-beurres", +"tape-cul", +"tape-culs", +"tape-dur", +"tape-durs", +"tape-à-l'oeil", +"tape-à-l'œil", +"tapis-brosse", +"tapis-de-caoutchouté", +"tapis-franc", +"tapis-francs", +"tapis-luge", +"tapis-luges", +"tapis-plain", +"tard-venus", +"tarn-et-garonnais", +"tarn-et-garonnaise", +"tarn-et-garonnaises", +"tarso-métatarse", +"tarso-métatarsien", +"tarton-raire", +"tate-mono", +"tate-monos", +"tau-fluvalinate", +"taupe-grillon", +"taupes-grillons", +"taxi-auto", +"taxi-automobile", +"taxi-brousse", +"taxi-girl", +"taxi-girls", +"taxi-vélo", +"taxis-brousse", +"taxis-vélos", +"taï-kadaï", +"taï-le", +"taï-nüa", +"tchado-burkinabé", +"tchado-centrafricain", +"tchado-egyptien", +"tchado-lybien", +"tchado-soudano-lybien", +"tchin-tchin", +"tchou-tchou", +"tchéco-slovaque", +"tchéco-slovaques", +"teach-in", +"teach-ins", +"tee-shirt", +"tee-shirts", +"teen-ager", +"teen-agers", +"teint-vin", +"teint-vins", +"teinture-mère", +"temporo-conchinien", +"temporo-superficiel", +"tensio-actif", +"tente-abri", +"tente-ménagerie", +"tentes-ménageries", +"ter-ter", +"terno-annulaire", +"terra-cotta", +"terra-forma", +"terra-formai", +"terra-formaient", +"terra-formais", +"terra-formait", +"terra-formant", +"terra-formas", +"terra-formasse", +"terra-formassent", +"terra-formasses", +"terra-formassiez", +"terra-formassions", +"terra-forme", +"terra-forment", +"terra-former", +"terra-formera", +"terra-formerai", +"terra-formeraient", +"terra-formerais", +"terra-formerait", +"terra-formeras", +"terra-formerez", +"terra-formeriez", +"terra-formerions", +"terra-formerons", +"terra-formeront", +"terra-formes", +"terra-formez", +"terra-formiez", +"terra-formions", +"terra-formons", +"terra-formâmes", +"terra-formât", +"terra-formâtes", +"terra-formèrent", +"terra-formé", +"terra-formée", +"terra-formées", +"terra-formés", +"terre-grièpe", +"terre-neuva", +"terre-neuvas", +"terre-neuve", +"terre-neuvien", +"terre-neuvienne", +"terre-neuviennes", +"terre-neuviens", +"terre-neuvier", +"terre-neuviers", +"terre-noix", +"terre-plein", +"terre-pleins", +"terre-à-terre", +"terret-bourret", +"terza-rima", +"test-match", +"test-matchs", +"test-objet", +"tette-chèvre", +"tette-chèvres", +"teuf-teuf", +"teuf-teufa", +"teuf-teufai", +"teuf-teufaient", +"teuf-teufais", +"teuf-teufait", +"teuf-teufant", +"teuf-teufas", +"teuf-teufasse", +"teuf-teufassent", +"teuf-teufasses", +"teuf-teufassiez", +"teuf-teufassions", +"teuf-teufe", +"teuf-teufent", +"teuf-teufer", +"teuf-teufera", +"teuf-teuferai", +"teuf-teuferaient", +"teuf-teuferais", +"teuf-teuferait", +"teuf-teuferas", +"teuf-teuferez", +"teuf-teuferiez", +"teuf-teuferions", +"teuf-teuferons", +"teuf-teuferont", +"teuf-teufes", +"teuf-teufez", +"teuf-teufiez", +"teuf-teufions", +"teuf-teufons", +"teuf-teufâmes", +"teuf-teufât", +"teuf-teufâtes", +"teuf-teufèrent", +"teuf-teufé", +"teufs-teufs", +"thifensulfuron-méthyle", +"thimistérien-clermontois", +"thiophanate-méthyl", +"thiophanate-éthyl", +"thon-samsonais", +"thoré-folléen", +"thoré-folléenne", +"thoré-folléennes", +"thoré-folléens", +"thoult-tronaisien", +"thoult-tronaisienne", +"thoult-tronaisiennes", +"thoult-tronaisiens", +"thraco-illyrienne", +"thuit-angevin", +"thuit-angevine", +"thuit-angevines", +"thuit-angevins", +"thuit-signolais", +"thuit-signolaise", +"thuit-signolaises", +"thuit-simérien", +"thuit-simérienne", +"thuit-simériennes", +"thuit-simériens", +"thun-episcopien", +"thun-épiscopien", +"thun-épiscopienne", +"thun-épiscopiennes", +"thun-épiscopiens", +"thézy-glimontois", +"thézy-glimontoise", +"thézy-glimontoises", +"thêta-jointure", +"thêta-jointures", +"ti-coune", +"ti-counes", +"ti-cul", +"ti-papoute", +"ti-punch", +"ti-punchs", +"tibio-malléolaire", +"tibéto-birman", +"tibéto-birmane", +"tibéto-birmanes", +"tibéto-birmans", +"tic-tac", +"tic-tac-toe", +"tic-tacs", +"ticket-restaurant", +"tie-break", +"tie-breaks", +"tierce-feuille", +"tierce-rime", +"tierces-rimes", +"tiger-kidnappeur", +"tiger-kidnapping", +"tiger-kidnappings", +"tigre-garou", +"tigres-garous", +"tiki-taka", +"tilleul-othonnais", +"tilleul-othonnaise", +"tilleul-othonnaises", +"tilt-shift", +"timbre-amende", +"timbre-poste", +"timbre-quittance", +"timbre-taxe", +"timbres-amende", +"timbres-poste", +"timbres-quittances", +"time-lapse", +"time-lapses", +"time-sharing", +"time-sharings", +"tiou-tiou", +"tiou-tious", +"tira-tutto", +"tireur-au-cul", +"tireurs-au-cul", +"tiroir-caisse", +"tiroirs-caisses", +"tissu-éponge", +"tissus-éponges", +"titan-cotte", +"titanico-ammonique", +"titanico-ammoniques", +"titre-service", +"titres-services", +"toba-qom", +"toc-feu", +"toc-toc", +"toc-tocs", +"tohu-bohu", +"tohu-bohus", +"tohus-bohus", +"toi-même", +"toit-terrasse", +"toits-terrasses", +"tolclofos-méthyl", +"tom-pouce", +"tom-tom", +"tom-toms", +"tombe-cartouche", +"tonne-grenoir", +"tonne-mètre", +"top-down", +"top-model", +"top-models", +"top-modèle", +"top-modèles", +"top-secret", +"top-secrets", +"topo-guide", +"topo-guides", +"toque-feu", +"torche-cul", +"torche-culs", +"torche-fer", +"torche-pertuis", +"torche-pin", +"torche-pinceau", +"torche-pinceaux", +"torche-pins", +"tord-boyau", +"tord-boyaux", +"tord-nez", +"tori-i", +"torse-poil", +"torse-poils", +"tortue-alligator", +"tortue-boite", +"tortue-boîte", +"tortue-duc", +"tortues-alligators", +"tortues-boites", +"tortues-boîtes", +"tortues-ducs", +"tosa-inu", +"tote-bag", +"tote-bags", +"touch-and-go", +"touche-pipi", +"touche-touche", +"touche-à-tout", +"touille-boeuf", +"touille-boeufs", +"touille-bœuf", +"touille-bœufs", +"tour-minute", +"tour-opérateur", +"tour-opérateurs", +"tour-opératrice", +"tour-opératrices", +"tour-à-tour", +"tourne-au-vent", +"tourne-case", +"tourne-cases", +"tourne-disque", +"tourne-disques", +"tourne-feuille", +"tourne-feuilles", +"tourne-feuillet", +"tourne-feuillets", +"tourne-fil", +"tourne-fils", +"tourne-gants", +"tourne-motte", +"tourne-mottes", +"tourne-oreille", +"tourne-oreilles", +"tourne-pierres", +"tourne-soc", +"tourne-socs", +"tourne-vent", +"tourne-vents", +"tourne-à-gauche", +"tourneur-fraiseur", +"tourneurs-fraiseurs", +"tours-minute", +"tours-opérateurs", +"tours-opératrices", +"tours-sur-marnais", +"tours-sur-marnaise", +"tours-sur-marnaises", +"tout-Londres", +"tout-blanc", +"tout-blancs", +"tout-communication", +"tout-connaissant", +"tout-en-un", +"tout-ensemble", +"tout-fait", +"tout-faits", +"tout-fécond", +"tout-parisien", +"tout-parisienne", +"tout-parisiennes", +"tout-parisiens", +"tout-petit", +"tout-petits", +"tout-puissant", +"tout-puissants", +"tout-terrain", +"tout-venant", +"tout-venu", +"tout-à-fait", +"tout-à-l'égout", +"tout-à-la-rue", +"toute-bonne", +"toute-bonté", +"toute-cousue", +"toute-petite", +"toute-présence", +"toute-puissance", +"toute-puissante", +"toute-saine", +"toute-science", +"toute-table", +"toute-venue", +"toute-épice", +"toutes-bonnes", +"toutes-boîtes", +"toutes-petites", +"toutes-puissantes", +"toutes-saines", +"toutes-tables", +"toutes-venues", +"toxi-infectieux", +"toxi-infection", +"toxi-infections", +"toy-terrier", +"trace-bouche", +"trace-roulis", +"trace-sautereau", +"trace-vague", +"trachée-artère", +"trachélo-occipital", +"trachéo-bronchite", +"trachéo-bronchites", +"trade-union", +"trade-unionisme", +"trade-unionismes", +"trade-unions", +"tragi-comique", +"tragi-comiques", +"tragi-comédie", +"tragi-comédies", +"train-train", +"train-trains", +"train-tram", +"traine-buche", +"traine-buches", +"traine-ruisseau", +"traine-savate", +"traine-savates", +"trains-trams", +"trait-d'union", +"trait-d'unioné", +"trait-track", +"tram-train", +"trams-trains", +"tran-tran", +"tranche-maçonné", +"tranche-montagne", +"tranche-montagnes", +"tranche-papier", +"tranche-tête", +"tranchées-abris", +"traîne-buisson", +"traîne-bâton", +"traîne-bûche", +"traîne-bûches", +"traîne-charrue", +"traîne-la-patte", +"traîne-lattes", +"traîne-malheur", +"traîne-misère", +"traîne-patins", +"traîne-potence", +"traîne-ruisseau", +"traîne-savate", +"traîne-savates", +"traîne-semelle", +"traîne-semelles", +"trench-coat", +"trench-coats", +"trente-cinq", +"trente-deux", +"trente-deuxième", +"trente-deuxièmes", +"trente-deuzain", +"trente-deuzains", +"trente-deuzet", +"trente-deuzets", +"trente-douze", +"trente-et-un", +"trente-et-une", +"trente-et-unième", +"trente-et-unièmes", +"trente-huit", +"trente-neuf", +"trente-neuvième", +"trente-quatre", +"trente-sept", +"trente-six", +"trente-trois", +"trente-troisième", +"tribo-électricité", +"tribo-électricités", +"tribo-électrique", +"tribo-électriques", +"tribénuron-méthyle", +"tric-trac", +"tric-tracs", +"trichloro-nitrométhane", +"trichloro-trinitro-benzène", +"triflusulfuron-méthyle", +"trinexapac-éthyl", +"trinitro-cellulose", +"trinitro-celluloses", +"tripe-madame", +"triple-croche", +"triples-croches", +"trique-madame", +"tris-mal", +"tris-male", +"tris-males", +"tris-maux", +"trois-bassinois", +"trois-bassinoise", +"trois-bassinoises", +"trois-crayons", +"trois-huit", +"trois-mâts", +"trois-mâts-goélettes", +"trois-pierrais", +"trois-pierraise", +"trois-pierraises", +"trois-ponts", +"trois-quarts", +"trois-riviérien", +"trois-riviérienne", +"trois-riviériennes", +"trois-riviériens", +"trois-roues", +"trois-six", +"trois-trois", +"trois-épines", +"trompe-cheval", +"trompe-couillon", +"trompe-l'oeil", +"trompe-l'œil", +"trompe-la-mort", +"trompe-oreilles", +"trompe-valet", +"trop-bu", +"trop-payé", +"trop-payés", +"trop-perçu", +"trop-perçus", +"trop-plein", +"trop-pleins", +"trotte-chemin", +"trotte-menu", +"trouble-fête", +"trouble-fêtes", +"trousse-barre", +"trousse-barres", +"trousse-pet", +"trousse-pets", +"trousse-pied", +"trousse-pieds", +"trousse-pète", +"trousse-pètes", +"trousse-queue", +"trousse-queues", +"trousse-traits", +"très-chrétien", +"très-haut", +"tré-flip", +"tré-flips", +"tré-sept", +"trépan-benne", +"trépan-bennes", +"tsoin-tsoin", +"tsouin-tsouin", +"tss-tss", +"tsé-tsé", +"tsé-tsés", +"tta-kun", +"tta-kuns", +"ttun-ttun", +"ttun-ttuns", +"tu-tu-ban-ban", +"tubéro-infundibulaire", +"tubéro-infundibulaires", +"tue-brebis", +"tue-chien", +"tue-chiens", +"tue-diable", +"tue-diables", +"tue-l'amour", +"tue-loup", +"tue-loups", +"tue-mouche", +"tue-mouches", +"tue-poule", +"tue-teignes", +"tue-vent", +"tuniso-égypto-lybien", +"tupi-guarani", +"turbo-alternateur", +"turbo-alternateurs", +"turbo-capitalisme", +"turbo-capitalismes", +"turbo-compresseur", +"turbo-compresseurs", +"turbo-prof", +"turbo-profs", +"turco-coréen", +"turco-mongol", +"turco-persan", +"turco-syrien", +"turn-over", +"tutti-frutti", +"tux-zillertal", +"twin-set", +"twin-sets", +"tz'utujil", +"tâte-au-pot", +"tâte-ferraille", +"tâte-poule", +"tâte-vin", +"tâte-vins", +"témoins-clés", +"téra-ampère", +"téra-ampères", +"téra-électron-volt", +"téra-électron-volts", +"térawatt-heure", +"térawatt-heures", +"térawatts-heures", +"téraélectron-volt", +"téraélectron-volts", +"tétra-atomique", +"tétrachloro-isophtalonitrile", +"tétrachlorodibenzo-p-dioxine", +"tétrachlorodibenzo-p-dioxines", +"tête-bleu", +"tête-bêche", +"tête-chèvre", +"tête-de-Maure", +"tête-de-More", +"tête-de-bécasse", +"tête-de-chat", +"tête-de-chats", +"tête-de-cheval", +"tête-de-clou", +"tête-de-coq", +"tête-de-loup", +"tête-de-maure", +"tête-de-moineau", +"tête-de-mort", +"tête-de-méduse", +"tête-de-serpent", +"tête-de-soufre", +"tête-ronde", +"tête-verte", +"tête-à-queue", +"tête-à-tête", +"têtes-de-Maure", +"têtes-de-chat", +"têtes-de-clou", +"têtes-de-loup", +"têtes-de-moineau", +"têtes-de-mort", +"têtes-de-méduse", +"têtes-vertes", +"tôt-fait", +"tôt-faits", +"u-commerce", +"ukiyo-e", +"ukiyo-es", +"unda-maris", +"une-deux", +"uni-dimensionnel", +"uni-dimensionnelle", +"uni-dimensionnelles", +"uni-dimensionnels", +"uni-modal", +"uni-sonore", +"uni-sonores", +"unité-souris", +"unités-souris", +"univers-bloc", +"univers-île", +"univers-îles", +"upa-upa", +"urane-mica", +"uranes-micas", +"uro-génital", +"uro-génitale", +"uro-génitales", +"uro-génitaux", +"urétro-cystotomie", +"urétro-cystotomies", +"uto-aztèque", +"uto-aztèques", +"utéro-lombaire", +"utéro-ovarien", +"utéro-ovarienne", +"utéro-ovariennes", +"utéro-ovariens", +"utéro-placentaire", +"utéro-tubaire", +"utéro-vaginal", +"utéro-vaginale", +"utéro-vaginales", +"utéro-vaginaux", +"uva-ursi", +"uva-ursis", +"v'là", +"v'nir", +"v'nu", +"v's", +"va-de-la-gueule", +"va-de-pied", +"va-et-vient", +"va-nu-pieds", +"va-outre", +"va-t'en", +"va-t-en", +"va-t-en-guerre", +"va-te-laver", +"va-tout", +"vache-biche", +"vache-garou", +"vaches-biches", +"vaches-garous", +"vade-in-pace", +"vade-mecum", +"vaeakau-taumako", +"vaeakau-taumakos", +"vagino-vésical", +"vaine-pâture", +"val-de-marnais", +"val-de-saânais", +"val-de-saânaise", +"val-de-saânaises", +"val-mésangeois", +"val-mésangeoise", +"val-mésangeoises", +"val-saint-germinois", +"val-saint-germinoise", +"val-saint-germinoises", +"val-saint-pierrais", +"val-saint-pierraise", +"val-saint-pierraises", +"valence-gramme", +"valence-grammes", +"valet-de-pied", +"valet-à-patin", +"valets-de-pied", +"valets-à-patin", +"valse-hésitation", +"valses-hésitations", +"vanity-case", +"vanity-cases", +"vas-y", +"vasculo-nerveux", +"vaso-constricteur", +"vaso-constricteurs", +"vaso-constriction", +"vaso-constrictions", +"vaso-dilatateur", +"vaso-dilatateurs", +"vaso-dilatation", +"vaso-dilatations", +"vaso-intestinal", +"vaso-intestinale", +"vaso-intestinales", +"vaso-intestinaux", +"vaso-moteur", +"vaso-motrice", +"vaterite-A", +"vaterite-As", +"vaux-champenois", +"vaux-champenoise", +"vaux-champenoises", +"vaux-chavannois", +"vaux-sûrois", +"veau-laq", +"veau-marin", +"velci-aller", +"venez-y-voir", +"ventre-madame", +"ventre-saint-gris", +"ver-coquin", +"verge-d'or", +"verges-d'or", +"vers-librisme", +"vers-librismes", +"vers-libriste", +"vers-libristes", +"vert-bois", +"vert-de-gris", +"vert-de-grisa", +"vert-de-grisai", +"vert-de-grisaient", +"vert-de-grisais", +"vert-de-grisait", +"vert-de-grisant", +"vert-de-grisas", +"vert-de-grisasse", +"vert-de-grisassent", +"vert-de-grisasses", +"vert-de-grisassiez", +"vert-de-grisassions", +"vert-de-grise", +"vert-de-grisent", +"vert-de-griser", +"vert-de-grisera", +"vert-de-griserai", +"vert-de-griseraient", +"vert-de-griserais", +"vert-de-griserait", +"vert-de-griseras", +"vert-de-griserez", +"vert-de-griseriez", +"vert-de-griserions", +"vert-de-griserons", +"vert-de-griseront", +"vert-de-grises", +"vert-de-grisez", +"vert-de-grisiez", +"vert-de-grisions", +"vert-de-grisons", +"vert-de-grisâmes", +"vert-de-grisât", +"vert-de-grisâtes", +"vert-de-grisèrent", +"vert-de-grisé", +"vert-de-grisée", +"vert-de-grisées", +"vert-de-grisés", +"vert-jaune", +"vert-monnier", +"vert-monniers", +"vesse-de-loup", +"vesses-de-loup", +"veston-cravate", +"vestons-cravates", +"vetula-domussien", +"vetula-domussienne", +"vetula-domussiennes", +"vetula-domussiens", +"vice-amiral", +"vice-amirale", +"vice-amirales", +"vice-amirauté", +"vice-amiraux", +"vice-bailli", +"vice-baillis", +"vice-camérier", +"vice-cardinal", +"vice-champion", +"vice-championne", +"vice-championnes", +"vice-champions", +"vice-chancelier", +"vice-chanceliers", +"vice-consul", +"vice-consulat", +"vice-consulats", +"vice-consule", +"vice-directeur", +"vice-gouverneur", +"vice-gérance", +"vice-gérances", +"vice-gérant", +"vice-gérants", +"vice-gérent", +"vice-gérents", +"vice-légat", +"vice-légation", +"vice-légations", +"vice-légats", +"vice-official", +"vice-procureur", +"vice-procureurs", +"vice-préfet", +"vice-présida", +"vice-présidai", +"vice-présidaient", +"vice-présidais", +"vice-présidait", +"vice-présidant", +"vice-présidas", +"vice-présidasse", +"vice-présidassent", +"vice-présidasses", +"vice-présidassiez", +"vice-présidassions", +"vice-préside", +"vice-présidence", +"vice-présidences", +"vice-président", +"vice-présidente", +"vice-présidentes", +"vice-présidents", +"vice-présider", +"vice-présidera", +"vice-présiderai", +"vice-présideraient", +"vice-présiderais", +"vice-présiderait", +"vice-présideras", +"vice-présiderez", +"vice-présideriez", +"vice-présiderions", +"vice-présiderons", +"vice-présideront", +"vice-présides", +"vice-présidez", +"vice-présidiez", +"vice-présidions", +"vice-présidons", +"vice-présidâmes", +"vice-présidât", +"vice-présidâtes", +"vice-présidèrent", +"vice-présidé", +"vice-présidée", +"vice-présidées", +"vice-présidés", +"vice-recteur", +"vice-recteurs", +"vice-rectrice", +"vice-rectrices", +"vice-reine", +"vice-reines", +"vice-roi", +"vice-rois", +"vice-royal", +"vice-royale", +"vice-royales", +"vice-royauté", +"vice-royautés", +"vice-royaux", +"vice-secrétaire", +"vice-sénéchal", +"vice-versa", +"vices-gouverneurs", +"victim-blaming", +"vide-atelier", +"vide-ateliers", +"vide-bouteille", +"vide-bouteilles", +"vide-cave", +"vide-caves", +"vide-citrons", +"vide-couilles", +"vide-dressing", +"vide-dressings", +"vide-gousset", +"vide-goussets", +"vide-grange", +"vide-grenier", +"vide-greniers", +"vide-maison", +"vide-maisons", +"vide-ordure", +"vide-ordures", +"vide-poche", +"vide-poches", +"vide-pomme", +"vide-pommes", +"vide-pommier", +"vide-vite", +"vieil-baugeois", +"vieil-baugeoise", +"vieil-baugeoises", +"vieil-hesdinois", +"vieil-hesdinoise", +"vieil-hesdinoises", +"viel-mauricien", +"viel-mauricienne", +"viel-mauriciennes", +"viel-mauriciens", +"vielle-soubiranais", +"vielle-soubiranaise", +"vielle-soubiranaises", +"viens-poupoulerie", +"viens-poupouleries", +"vif-argent", +"vif-gage", +"vigne-blanche", +"vignes-blanches", +"village-rue", +"village-tas", +"villages-rue", +"villages-rues", +"villages-tas", +"villard-d'hérien", +"villard-d'hérienne", +"villard-d'hériennes", +"villard-d'hériens", +"villard-de-lans", +"villes-champignons", +"villes-clés", +"villes-provinces", +"villes-États", +"vingt-cinq", +"vingt-cinquième", +"vingt-cinquièmes", +"vingt-deux", +"vingt-deuxain", +"vingt-deuxains", +"vingt-deuxième", +"vingt-deuxièmes", +"vingt-et-un", +"vingt-et-une", +"vingt-et-unième", +"vingt-et-unièmes", +"vingt-hanapsien", +"vingt-hanapsienne", +"vingt-hanapsiennes", +"vingt-hanapsiens", +"vingt-huit", +"vingt-huitième", +"vingt-huitièmes", +"vingt-neuf", +"vingt-neuvième", +"vingt-neuvièmes", +"vingt-quatrain", +"vingt-quatrains", +"vingt-quatre", +"vingt-quatrième", +"vingt-quatrièmes", +"vingt-sept", +"vingt-septième", +"vingt-septièmes", +"vingt-six", +"vingt-sixain", +"vingt-sixième", +"vingt-sixièmes", +"vingt-trois", +"vingt-troisième", +"vingt-troisièmes", +"vino-benzoïque", +"vino-benzoïques", +"violet-évêque", +"viorne-tin", +"viornes-tin", +"vire-capot", +"vire-capots", +"vire-vire", +"vis-à-vis", +"visa-bourgien", +"visa-bourgienne", +"visa-bourgiennes", +"visa-bourgiens", +"visuo-spacial", +"visuo-spaciale", +"visuo-spaciales", +"visuo-spaciaux", +"vit-de-mulet", +"vivaro-alpin", +"vivaro-alpins", +"vive-eau", +"vive-la-joie", +"vives-eaux", +"vivre-ensemble", +"voile-manteau", +"voile-manteaux", +"vois-tu", +"voiture-bar", +"voiture-bélier", +"voiture-cage", +"voiture-couchettes", +"voiture-lits", +"voiture-pilote", +"voiture-restaurant", +"voiture-salon", +"voiture-ventouse", +"voitures-balais", +"voitures-bars", +"voitures-béliers", +"voitures-cages", +"voitures-couchettes", +"voitures-lits", +"voitures-pilotes", +"voitures-restaurants", +"voitures-salons", +"voitures-ventouses", +"vol-au-vent", +"vol-bélier", +"vol-béliers", +"volley-ball", +"volley-balls", +"volt-ampère", +"volt-ampères", +"volte-face", +"volte-faces", +"vomito-negro", +"vomito-négro", +"vous-même", +"vous-mêmes", +"voyageur-kilomètre", +"voyageurs-kilomètres", +"voyez-vous", +"vrigne-meusien", +"vrigne-meusienne", +"vrigne-meusiennes", +"vrigne-meusiens", +"vu-arriver", +"vy-les-luron", +"vy-les-lurone", +"vy-les-lurones", +"vy-les-lurons", +"végéto-sulfurique", +"vélo-rail", +"vélo-rails", +"vélo-taxi", +"vélo-école", +"vélo-écoles", +"vélos-taxis", +"vétéro-testamentaire", +"vétéro-testamentaires", +"w.-c.", +"wagon-bar", +"wagon-citerne", +"wagon-couchette", +"wagon-couchettes", +"wagon-foudre", +"wagon-grue", +"wagon-lit", +"wagon-lits", +"wagon-poche", +"wagon-poste", +"wagon-restaurant", +"wagon-réservoir", +"wagon-salon", +"wagon-tombereau", +"wagon-trémie", +"wagon-vanne", +"wagons-bars", +"wagons-citernes", +"wagons-couchettes", +"wagons-foudres", +"wagons-grues", +"wagons-lits", +"wagons-restaurants", +"wagons-réservoirs", +"wagons-salons", +"wagons-tombereaux", +"wagons-trémie", +"wah-wah", +"walkie-talkie", +"walkies-talkies", +"wallon-cappelois", +"wallon-cappeloise", +"wallon-cappeloises", +"waray-waray", +"water-ballast", +"water-ballasts", +"water-closet", +"water-closets", +"water-polo", +"water-proof", +"water-proofs", +"wauthier-brainois", +"waux-hall", +"waux-halls", +"waza-ari", +"web-to-print", +"week-end", +"week-ends", +"wemaers-cappelois", +"wemaers-cappeloise", +"wemaers-cappeloises", +"wesh-wesh", +"west-cappelois", +"west-cappeloise", +"west-cappeloises", +"white-spirit", +"willy-willy", +"witsuwit'en", +"wuchiaping'ien", +"y'a", +"yacht-club", +"yacht-clubs", "yin-yang", "ylang-ylang", -"yocto-ohm", -"yocto-ohms", -"Yo-kai", -"Yorkshire-et-Humber", -"yotta-ampère", -"yotta-ampères", -"young-ice", -"young-ices", -"you-you", -"you-yous", "yo-yo", "yo-yota", "yo-yotai", "yo-yotaient", "yo-yotais", "yo-yotait", -"yo-yotâmes", "yo-yotant", "yo-yotas", "yo-yotasse", @@ -26128,12 +31108,7 @@ FR_BASE_EXCEPTIONS = [ "yo-yotasses", "yo-yotassiez", "yo-yotassions", -"yo-yotât", -"yo-yotâtes", "yo-yote", -"yo-yoté", -"yo-yotée", -"yo-yotées", "yo-yotent", "yo-yoter", "yo-yotera", @@ -26142,97 +31117,42 @@ FR_BASE_EXCEPTIONS = [ "yo-yoterais", "yo-yoterait", "yo-yoteras", -"yo-yotèrent", "yo-yoterez", "yo-yoteriez", "yo-yoterions", "yo-yoterons", "yo-yoteront", "yo-yotes", -"yo-yotés", "yo-yotez", "yo-yotiez", "yo-yotions", "yo-yotons", -"Ypreville-Biville", -"Yronde-et-Buron", -"Yssac-la-Tourette", +"yo-yotâmes", +"yo-yotât", +"yo-yotâtes", +"yo-yotèrent", +"yo-yoté", +"yo-yotée", +"yo-yotées", +"yo-yotés", +"yocto-ohm", +"yocto-ohms", +"yotta-ampère", +"yotta-ampères", +"you-you", +"you-yous", +"young-ice", +"young-ices", "yuki-onna", "yuki-onnas", -"Yverdon-les-Bains", -"Yves-Gomezée", -"Yvetot-Bocage", -"Yvignac-la-Tour", -"Yville-sur-Seine", -"Yvoy-le-Marron", -"Yvrac-et-Malleyrand", -"Yvré-le-Pôlin", -"Yvré-l'Evêque", -"Yvré-l'Évêque", -"Yzeures-sur-Creuse", -"Z9-12:Ac", -"Z9-dodécénylacétate", -"Zahna-Elster", -"zapil's", -"zapil'ser", -"zayse-zergulla", -"Z/E-8-DDA", -"zébré-de-vert", -"Zella-Mehlis", -"Zeltingen-Rachtig", +"yé-yé", "z'en", -"Zend-avesta", -"zénith-secteur", -"zénith-secteurs", -"zepto-ohm", -"zepto-ohms", -"Zernitz-Lohm", -"zéro-dimensionnel", -"Zétrud-Lumay", -"zetta-ampère", -"zetta-ampères", -"Zeulenroda-Triebes", -"Zevenhuizen-Moerkapelle", -"Z-grille", -"Z-grilles", -"Zichen-Zussen-Bolder", -"Ziegra-Knobelsdorf", -"Zihlschlacht-Sitterdorf", -"Zillis-Reischen", -"zinc-blende", -"zinc-blendes", -"Ziortza-Bolibar", -"zizi-panpan", -"Zoerle-Parwijs", -"Zoeterwoude-Dorp", -"Zoeterwoude-Rijndijk", -"zones-clés", -"zoo-cinéma", -"zoo-cinémas", -"zoo-gymnaste", -"zoo-gymnastes", -"Zschaitz-Ottewig", -"Zuid-Beijerland", -"Zuid-Eierland", -"Zuid-Polsbroek", -"Zuid-Scharwoude", -"Zuid-Spierdijk", -"Zuid-Waddinxveen", -"zulgo-gemzek", -"zuricho-montpelliérain", -"zuricho-montpelliéraine", -"zuricho-montpelliéraines", -"zuricho-montpelliérains", -"zut-au-berger", -"Zwaagdijk-Oost", -"Zwaagdijk-West", "z'y", "z'yeuta", "z'yeutai", "z'yeutaient", "z'yeutais", "z'yeutait", -"z'yeutâmes", "z'yeutant", "z'yeutas", "z'yeutasse", @@ -26240,12 +31160,7 @@ FR_BASE_EXCEPTIONS = [ "z'yeutasses", "z'yeutassiez", "z'yeutassions", -"z'yeutât", -"z'yeutâtes", "z'yeute", -"z'yeuté", -"z'yeutée", -"z'yeutées", "z'yeutent", "z'yeuter", "z'yeutera", @@ -26254,42 +31169,427 @@ FR_BASE_EXCEPTIONS = [ "z'yeuterais", "z'yeuterait", "z'yeuteras", -"z'yeutèrent", "z'yeuterez", "z'yeuteriez", "z'yeuterions", "z'yeuterons", "z'yeuteront", "z'yeutes", -"z'yeutés", "z'yeutez", "z'yeutiez", "z'yeutions", "z'yeutons", +"z'yeutâmes", +"z'yeutât", +"z'yeutâtes", +"z'yeutèrent", +"z'yeuté", +"z'yeutée", +"z'yeutées", +"z'yeutés", "z'yeux", +"zapil's", +"zapil'ser", +"zayse-zergulla", +"zepto-ohm", +"zepto-ohms", +"zetta-ampère", +"zetta-ampères", +"zinc-blende", +"zinc-blendes", +"zizi-panpan", +"zones-clés", +"zoo-cinéma", +"zoo-cinémas", +"zoo-gymnaste", +"zoo-gymnastes", +"zulgo-gemzek", +"zuricho-montpelliérain", +"zuricho-montpelliéraine", +"zuricho-montpelliéraines", +"zuricho-montpelliérains", +"zut-au-berger", +"zy-va", +"zy-vas", "zygomato-auriculaire", "zygomato-labial", "zygomato-maxillaire", -"zy-va", -"zy-vas", -"α-Dahllite", -"α-Dahllites", +"zébré-de-vert", +"zénith-secteur", +"zénith-secteurs", +"zéro-dimensionnel", +"Œuf-en-Ternois", +"Ében-Émael", +"Écalles-Alix", +"Écardenville-la-Campagne", +"Écardenville-sur-Eure", +"Écaussinnes-Lalaing", +"Écaussinnes-d'Enghien", +"Échelle-Saint-Aurin", +"Échenans-sous-Mont-Vaudois", +"Échenoz-la-Méline", +"Échenoz-le-Sec", +"Éclans-Nenon", +"Éclaron-Braucourt-Sainte-Livière", +"Éclusier-Vaux", +"École-Valentin", +"Écot-la-Combe", +"Écotay-l'Olme", +"Écouché-les-Vallées", +"Écourt-Saint-Quentin", +"Écoust-Saint-Mein", +"Écoute-s'il-pleut", +"Écretteville-lès-Baons", +"Écretteville-sur-Mer", +"Écry-le-Franc", +"Écurey-en-Verdunois", +"Écury-le-Repos", +"Écury-sur-Coole", +"Édouard-Josse", +"Église-Neuve-d'Issac", +"Église-Neuve-de-Vergt", +"Église-aux-Bois", +"Égliseneuve-d'Entraigues", +"Égliseneuve-des-Liards", +"Égliseneuve-près-Billom", +"Égriselles-le-Bocage", +"Éguille-sur-Seudre", +"Éguilly-sous-Bois", +"Éguzon-Chantôme", +"Égée-Méridionale", +"Égée-Septentrionale", +"Éhein-bas", +"Éleu-dit-Leauwette", +"Élincourt-Sainte-Marguerite", +"Élise-Daucourt", +"Émilie-Romagne", +"Émilien-Romagnol", +"Émilienne-Romagnole", +"Émiliennes-Romagnoles", +"Émiliens-Romagnols", +"Énencourt-Léage", +"Énencourt-le-Sec", +"Éole-en-Beauce", +"Épagne-Épagnette", +"Épaux-Bézu", +"Épeigné-les-Bois", +"Épeigné-sur-Dême", +"Épercieux-Saint-Paul", +"Épernay-sous-Gevrey", +"Épi-Contois", +"Épi-Contoise", +"Épi-Contoises", +"Épiais-Rhus", +"Épiais-lès-Louvres", +"Épieds-en-Beauce", +"Épiez-sur-Chiers", +"Épiez-sur-Meuse", +"Épinac-les-Mines", +"Épinay-Champlâtreux", +"Épinay-le-Comte", +"Épinay-sous-Sénart", +"Épinay-sur-Duclair", +"Épinay-sur-Odon", +"Épinay-sur-Orge", +"Épinay-sur-Seine", +"Épine-aux-Bois", +"Épineau-les-Voves", +"Épineu-le-Chevreuil", +"Épineuil-le-Fleuriel", +"Épineux-le-Seguin", +"Épreville-en-Lieuvin", +"Épreville-en-Roumois", +"Épreville-près-le-Neubourg", +"Équatoria-Central", +"Équatoria-Occidental", +"Équatoria-Oriental", +"Équennes-Éramecourt", +"Équeurdreville-Hainneville", +"Équihen-Plage", +"Éragny-sur-Epte", +"Éragny-sur-Oise", +"Érize-Saint-Dizier", +"Érize-la-Brûlée", +"Érize-la-Grande", +"Érize-la-Petite", +"Étables-sur-Mer", +"Étais-la-Sauvin", +"Étampes-sur-Marne", +"Étang-Bertrand", +"Étang-Salé", +"Étang-Saléen", +"Étang-Saléenne", +"Étang-Saléennes", +"Étang-Saléens", +"Étang-Vergy", +"Étang-la-Ville", +"Étang-sur-Arroux", +"État-Major", +"État-major", +"État-nation", +"État-nounou", +"État-providence", +"États-Généraux", +"États-Majors", +"États-Unien", +"États-Unienne", +"États-Uniennes", +"États-Uniens", +"États-Unis", +"États-majors", +"États-nations", +"États-nounous", +"États-providence", +"Étaves-et-Bocquiaux", +"Étinehem-Méricourt", +"Étival-Clairefontaine", +"Étival-lès-le-Mans", +"Étoile-Saint-Cyrice", +"Étoile-sur-Rhône", +"Étrelles-et-la-Montbleuse", +"Étrelles-sur-Aube", +"Étricourt-Manancourt", +"Étricourt-Manancourtois", +"Étricourt-Manancourtoise", +"Étricourt-Manancourtoises", +"Étueffont-Bas", +"Évaux-et-Ménil", +"Évaux-les-Bains", +"Évette-Salbert", +"Évian-les-Bains", +"Évin-Malmaison", +"Évry-Grégy-sur-Yerre", +"Évry-Petit-Bourg", +"Ézy-sur-Eure", +"Î.-P.-É.", +"Île-Bouchard", +"Île-Molène", +"Île-Rousse", +"Île-Saint-Denis", +"Île-Tudiste", +"Île-Tudistes", +"Île-Tudy", +"Île-aux-Moines", +"Île-d'Aix", +"Île-d'Anticosti", +"Île-d'Arz", +"Île-d'Elle", +"Île-d'Houat", +"Île-d'Olonne", +"Île-d'Yeu", +"Île-de-Batz", +"Île-de-Bréhat", +"Île-de-France", +"Île-de-Sein", +"Île-du-Prince-Édouard", +"Îles-de-la-Madeleine", +"Îlo-Dionysien", +"Îlo-Dionysienne", +"Ölbronn-Dürrn", +"Übach-Palenberg", +"Ühlingen-Birkendorf", +"âme-sœur", +"âmes-sœurs", +"âne-zèbre", +"ânes-zèbres", +"ça-va-ça-vient", +"ça-voir", +"ça-voirs", +"çui-là", +"écart-type", +"écarts-types", +"écho-location", +"écho-locations", +"échos-radars", +"écorche-œil", +"écoute-s'il-pleut", +"écrase-merde", +"écrase-merdes", +"écurie-ménagerie", +"écuries-ménageries", +"égal-à-tous", +"église-halle", +"égypto-lybien", +"égypto-tchado-soudanais", +"éka-actinide", +"éka-actinides", +"éka-aluminium", +"éka-astate", +"éka-bismuth", +"éka-bore", +"éka-borium", +"éka-francium", +"éka-mercure", +"éka-plomb", +"éka-polonium", +"éka-prométhium", +"éka-silicium", +"électron-volt", +"électron-volts", +"élément-clé", +"éléments-clés", +"émetteur-récepteur", +"émetteur-récepteurs", +"émilien-romagnol", +"émilienne-romagnole", +"émiliennes-romagnoles", +"émiliens-romagnols", +"émirato-algérien", +"émirato-allemand", +"émirato-allemands", +"émirato-britannique", +"émirato-britanniques", +"émirato-helvétique", +"émirato-helvétiques", +"émirato-indien", +"émirato-iranien", +"émirato-japonais", +"émission-débat", +"énargite-beta", +"énargite-betas", +"éoli-harpe", +"épargne-logement", +"épaulé-jeté", +"épaulés-jetés", +"épi-contois", +"épi-contoise", +"épi-contoises", +"épidote-gris", +"épinard-fraise", +"épine-du-Christ", +"épine-fleurie", +"épine-vinette", +"épines-vinettes", +"épiplo-entérocèle", +"épiplo-ischiocèle", +"épiplo-mérocèle", +"épluche-légume", +"épluche-légumes", +"épuise-volante", +"épuises-volantes", +"équato-guinéen", +"équato-guinéenne", +"équato-guinéennes", +"équato-guinéens", +"éso-narthex", +"étalon-or", +"étang-saléen", +"étang-saléenne", +"étang-saléennes", +"étang-saléens", +"état-limite", +"état-major", +"états-civils", +"états-généraux", +"états-limites", +"états-majors", +"états-nations", +"états-unianisa", +"états-unianisai", +"états-unianisaient", +"états-unianisais", +"états-unianisait", +"états-unianisant", +"états-unianisas", +"états-unianisasse", +"états-unianisassent", +"états-unianisasses", +"états-unianisassiez", +"états-unianisassions", +"états-unianise", +"états-unianisent", +"états-unianiser", +"états-unianisera", +"états-unianiserai", +"états-unianiseraient", +"états-unianiserais", +"états-unianiserait", +"états-unianiseras", +"états-unianiserez", +"états-unianiseriez", +"états-unianiserions", +"états-unianiserons", +"états-unianiseront", +"états-unianises", +"états-unianisez", +"états-unianisiez", +"états-unianisions", +"états-unianisons", +"états-unianisâmes", +"états-unianisât", +"états-unianisâtes", +"états-unianisèrent", +"états-unianisé", +"états-unianisée", +"états-unianisées", +"états-unianisés", +"états-unien", +"états-unienne", +"états-uniennes", +"états-uniens", +"étau-limeur", +"étaux-limeurs", +"éthane-1,2-diol", +"éthyl-benzène", +"éthéro-chloroforme", +"étouffe-chrétien", +"étouffe-chrétiens", +"étrangle-chat", +"étrangle-chien", +"étrangle-loup", +"étrangle-loups", +"étricourt-manancourtois", +"étricourt-manancourtoise", +"étricourt-manancourtoises", +"être-en-soi", +"être-là", +"êtres-en-soi", +"île-de-France", +"île-prison", +"île-tudiste", +"île-tudistes", +"île-État", +"îles-prisons", +"îles-États", +"îlo-dionysien", +"ôte-agrafes", +"über-célèbre", +"über-célèbres", +"Łutselk'e", +"Œuf-en-Ternois", +"œil-de-bœuf", +"œil-de-chat", +"œil-de-perdrix", +"œil-de-pie", +"œil-de-serpent", +"œil-de-tigre", +"œil-du-soleil", +"œils-de-bœuf", +"œils-de-pie", +"œils-de-serpent", +"œils-de-tigre", +"œsophago-gastro-duodénoscopie", +"œsophago-gastro-duodénoscopies", +"œuf-coque", +"œufs-coque", "α-D-glucofuranose", "α-D-glucopyranose", "α-D-ribofuranose", "α-D-ribopyranose", +"α-Dahllite", +"α-Dahllites", "α-L-ribofuranose", "α-L-ribopyranose", -"β-Dahllite", -"β-Dahllites", "β-D-glucofuranose", "β-D-glucopyranose", "β-D-ribofuranose", "β-D-ribopyranose", -"β-galactosidase", -"β-lactamine", +"β-Dahllite", +"β-Dahllites", "β-L-ribofuranose", "β-L-ribopyranose", +"β-galactosidase", +"β-lactamine", "β-sitostérol", "β-sitostérols", "γ-Dahllite", @@ -26299,4 +31599,5 @@ FR_BASE_EXCEPTIONS = [ "σ-additivités", "σ-compacité", "σ-compact", -"σ-compacts"] +"σ-compacts" +] diff --git a/spacy/lang/fr/syntax_iterators.py b/spacy/lang/fr/syntax_iterators.py new file mode 100644 index 000000000..c9de4f084 --- /dev/null +++ b/spacy/lang/fr/syntax_iterators.py @@ -0,0 +1,42 @@ +# coding: utf8 +from __future__ import unicode_literals + +from ...symbols import NOUN, PROPN, PRON + + +def noun_chunks(obj): + """ + Detect base noun phrases from a dependency parse. Works on both Doc and Span. + """ + labels = ['nsubj', 'nsubj:pass', 'obj', 'iobj', 'ROOT', 'appos', 'nmod', 'nmod:poss'] + doc = obj.doc # Ensure works on both Doc and Span. + np_deps = [doc.vocab.strings[label] for label in labels] + conj = doc.vocab.strings.add('conj') + np_label = doc.vocab.strings.add('NP') + seen = set() + for i, word in enumerate(obj): + if word.pos not in (NOUN, PROPN, PRON): + continue + # Prevent nested chunks from being produced + if word.i in seen: + continue + if word.dep in np_deps: + if any(w.i in seen for w in word.subtree): + continue + seen.update(j for j in range(word.left_edge.i, word.right_edge.i+1)) + yield word.left_edge.i, word.right_edge.i+1, np_label + elif word.dep == conj: + head = word.head + while head.dep == conj and head.head.i < head.i: + head = head.head + # If the head is an NP, and we're coordinated to it, we're an NP + if head.dep in np_deps: + if any(w.i in seen for w in word.subtree): + continue + seen.update(j for j in range(word.left_edge.i, word.right_edge.i+1)) + yield word.left_edge.i, word.right_edge.i+1, np_label + + +SYNTAX_ITERATORS = { + 'noun_chunks': noun_chunks +} diff --git a/spacy/lang/he/__init__.py b/spacy/lang/he/__init__.py index 4ed1f30d0..a15dc9a05 100644 --- a/spacy/lang/he/__init__.py +++ b/spacy/lang/he/__init__.py @@ -9,15 +9,17 @@ from ...attrs import LANG from ...util import update_exc +class HebrewDefaults(Language.Defaults): + lex_attr_getters = dict(Language.Defaults.lex_attr_getters) + lex_attr_getters[LANG] = lambda text: 'he' + + tokenizer_exceptions = update_exc(BASE_EXCEPTIONS) + stop_words = set(STOP_WORDS) + + class Hebrew(Language): lang = 'he' - - class Defaults(Language.Defaults): - lex_attr_getters = dict(Language.Defaults.lex_attr_getters) - lex_attr_getters[LANG] = lambda text: 'he' - - tokenizer_exceptions = update_exc(BASE_EXCEPTIONS) - stop_words = set(STOP_WORDS) + Defaults = HebrewDefaults __all__ = ['Hebrew'] diff --git a/spacy/lang/hu/__init__.py b/spacy/lang/hu/__init__.py index 7233239da..0fe6a9f5c 100644 --- a/spacy/lang/hu/__init__.py +++ b/spacy/lang/hu/__init__.py @@ -7,29 +7,33 @@ from .stop_words import STOP_WORDS from .lemmatizer import LOOKUP from ..tokenizer_exceptions import BASE_EXCEPTIONS +from ..norm_exceptions import BASE_NORMS from ...language import Language from ...lemmatizerlookup import Lemmatizer -from ...attrs import LANG -from ...util import update_exc +from ...attrs import LANG, NORM +from ...util import update_exc, add_lookups + + +class HungarianDefaults(Language.Defaults): + lex_attr_getters = dict(Language.Defaults.lex_attr_getters) + lex_attr_getters[LANG] = lambda text: 'hu' + lex_attr_getters[NORM] = add_lookups(Language.Defaults.lex_attr_getters[NORM], BASE_NORMS) + + tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) + stop_words = set(STOP_WORDS) + prefixes = tuple(TOKENIZER_PREFIXES) + suffixes = tuple(TOKENIZER_SUFFIXES) + infixes = tuple(TOKENIZER_INFIXES) + token_match = TOKEN_MATCH + + @classmethod + def create_lemmatizer(cls, nlp=None): + return Lemmatizer(LOOKUP) class Hungarian(Language): lang = 'hu' - - class Defaults(Language.Defaults): - lex_attr_getters = dict(Language.Defaults.lex_attr_getters) - lex_attr_getters[LANG] = lambda text: 'hu' - - tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) - stop_words = set(STOP_WORDS) - prefixes = tuple(TOKENIZER_PREFIXES) - suffixes = tuple(TOKENIZER_SUFFIXES) - infixes = tuple(TOKENIZER_INFIXES) - token_match = TOKEN_MATCH - - @classmethod - def create_lemmatizer(cls, nlp=None): - return Lemmatizer(LOOKUP) + Defaults = HungarianDefaults __all__ = ['Hungarian'] diff --git a/spacy/lang/hu/punctuation.py b/spacy/lang/hu/punctuation.py index 27a2912e2..ce6134927 100644 --- a/spacy/lang/hu/punctuation.py +++ b/spacy/lang/hu/punctuation.py @@ -1,18 +1,18 @@ # coding: utf8 from __future__ import unicode_literals -from ..punctuation import TOKENIZER_INFIXES -from ..char_classes import LIST_PUNCT, LIST_ELLIPSES, LIST_QUOTES, CURRENCY +from ..char_classes import LIST_PUNCT, LIST_ELLIPSES, LIST_QUOTES from ..char_classes import QUOTES, UNITS, ALPHA, ALPHA_LOWER, ALPHA_UPPER +LIST_ICONS = [r'[\p{So}--[°]]'] _currency = r'\$|¢|£|€|¥|฿' _quotes = QUOTES.replace("'", '') +_prefixes = ([r'\+'] + LIST_PUNCT + LIST_ELLIPSES + LIST_QUOTES + LIST_ICONS + + [r'[,.:](?=[{a}])'.format(a=ALPHA)]) -_prefixes = ([r'\+'] + LIST_PUNCT + LIST_ELLIPSES + LIST_QUOTES) - -_suffixes = (LIST_PUNCT + LIST_ELLIPSES + LIST_QUOTES + +_suffixes = (LIST_PUNCT + LIST_ELLIPSES + LIST_QUOTES + LIST_ICONS + [r'(?<=[0-9])\+', r'(?<=°[FfCcKk])\.', r'(?<=[0-9])(?:{})'.format(_currency), @@ -20,16 +20,14 @@ _suffixes = (LIST_PUNCT + LIST_ELLIPSES + LIST_QUOTES + r'(?<=[{}{}{}(?:{})])\.'.format(ALPHA_LOWER, r'%²\-\)\]\+', QUOTES, _currency), r'(?<=[{})])-e'.format(ALPHA_LOWER)]) - -_infixes = (LIST_ELLIPSES + +_infixes = (LIST_ELLIPSES + LIST_ICONS + [r'(?<=[{}])\.(?=[{}])'.format(ALPHA_LOWER, ALPHA_UPPER), - r'(?<=[{a}]),(?=[{a}])'.format(a=ALPHA), + r'(?<=[{a}])[,!?](?=[{a}])'.format(a=ALPHA), r'(?<=[{a}"])[:<>=](?=[{a}])'.format(a=ALPHA), r'(?<=[{a}])--(?=[{a}])'.format(a=ALPHA), r'(?<=[{a}]),(?=[{a}])'.format(a=ALPHA), r'(?<=[{a}])([{q}\)\]\(\[])(?=[\-{a}])'.format(a=ALPHA, q=_quotes)]) - TOKENIZER_PREFIXES = _prefixes TOKENIZER_SUFFIXES = _suffixes TOKENIZER_INFIXES = _infixes diff --git a/spacy/lang/it/__init__.py b/spacy/lang/it/__init__.py index 93f7f7764..7cc717cb3 100644 --- a/spacy/lang/it/__init__.py +++ b/spacy/lang/it/__init__.py @@ -5,25 +5,29 @@ from .stop_words import STOP_WORDS from .lemmatizer import LOOKUP from ..tokenizer_exceptions import BASE_EXCEPTIONS +from ..norm_exceptions import BASE_NORMS from ...language import Language from ...lemmatizerlookup import Lemmatizer -from ...attrs import LANG -from ...util import update_exc +from ...attrs import LANG, NORM +from ...util import update_exc, add_lookups + + +class ItalianDefaults(Language.Defaults): + lex_attr_getters = dict(Language.Defaults.lex_attr_getters) + lex_attr_getters[LANG] = lambda text: 'it' + lex_attr_getters[NORM] = add_lookups(Language.Defaults.lex_attr_getters[NORM], BASE_NORMS) + + tokenizer_exceptions = update_exc(BASE_EXCEPTIONS) + stop_words = set(STOP_WORDS) + + @classmethod + def create_lemmatizer(cls, nlp=None): + return Lemmatizer(LOOKUP) class Italian(Language): lang = 'it' - - class Defaults(Language.Defaults): - lex_attr_getters = dict(Language.Defaults.lex_attr_getters) - lex_attr_getters[LANG] = lambda text: 'it' - - tokenizer_exceptions = update_exc(BASE_EXCEPTIONS) - stop_words = set(STOP_WORDS) - - @classmethod - def create_lemmatizer(cls, nlp=None): - return Lemmatizer(LOOKUP) + Defaults = ItalianDefaults __all__ = ['Italian'] diff --git a/spacy/lang/lex_attrs.py b/spacy/lang/lex_attrs.py index e6378a5a4..4c3284b1e 100644 --- a/spacy/lang/lex_attrs.py +++ b/spacy/lang/lex_attrs.py @@ -125,7 +125,7 @@ def word_shape(text): LEX_ATTRS = { attrs.LOWER: lambda string: string.lower(), - attrs.NORM: lambda string: string, + attrs.NORM: lambda string: string.lower(), attrs.PREFIX: lambda string: string[0], attrs.SUFFIX: lambda string: string[-3:], attrs.CLUSTER: lambda string: 0, diff --git a/spacy/lang/nb/__init__.py b/spacy/lang/nb/__init__.py index 20832bfe3..c1b4af263 100644 --- a/spacy/lang/nb/__init__.py +++ b/spacy/lang/nb/__init__.py @@ -6,20 +6,24 @@ from .stop_words import STOP_WORDS from .morph_rules import MORPH_RULES from ..tokenizer_exceptions import BASE_EXCEPTIONS +from ..norm_exceptions import BASE_NORMS from ...language import Language -from ...attrs import LANG -from ...util import update_exc +from ...attrs import LANG, NORM +from ...util import update_exc, add_lookups + + +class NorwegianDefaults(Language.Defaults): + lex_attr_getters = dict(Language.Defaults.lex_attr_getters) + lex_attr_getters[LANG] = lambda text: 'nb' + lex_attr_getters[NORM] = add_lookups(Language.Defaults.lex_attr_getters[NORM], BASE_NORMS) + + tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) + stop_words = set(STOP_WORDS) class Norwegian(Language): lang = 'nb' - - class Defaults(Language.Defaults): - lex_attr_getters = dict(Language.Defaults.lex_attr_getters) - lex_attr_getters[LANG] = lambda text: 'nb' - - tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) - stop_words = set(STOP_WORDS) + Defaults = NorwegianDefaults __all__ = ['Norwegian'] diff --git a/spacy/lang/nl/__init__.py b/spacy/lang/nl/__init__.py index 254849ad0..7b948f295 100644 --- a/spacy/lang/nl/__init__.py +++ b/spacy/lang/nl/__init__.py @@ -4,21 +4,24 @@ from __future__ import unicode_literals from .stop_words import STOP_WORDS from ..tokenizer_exceptions import BASE_EXCEPTIONS +from ..norm_exceptions import BASE_NORMS from ...language import Language -from ...attrs import LANG -from ...util import update_exc +from ...attrs import LANG, NORM +from ...util import update_exc, add_lookups +class DutchDefaults(Language.Defaults): + lex_attr_getters = dict(Language.Defaults.lex_attr_getters) + lex_attr_getters[LANG] = lambda text: 'nl' + lex_attr_getters[NORM] = add_lookups(Language.Defaults.lex_attr_getters[NORM], BASE_NORMS) + + tokenizer_exceptions = update_exc(BASE_EXCEPTIONS) + stop_words = set(STOP_WORDS) + class Dutch(Language): lang = 'nl' - - class Defaults(Language.Defaults): - lex_attr_getters = dict(Language.Defaults.lex_attr_getters) - lex_attr_getters[LANG] = lambda text: 'nl' - - tokenizer_exceptions = update_exc(BASE_EXCEPTIONS) - stop_words = set(STOP_WORDS) + Defaults = DutchDefaults __all__ = ['Dutch'] diff --git a/spacy/lang/norm_exceptions.py b/spacy/lang/norm_exceptions.py new file mode 100644 index 000000000..b02dda2c8 --- /dev/null +++ b/spacy/lang/norm_exceptions.py @@ -0,0 +1,46 @@ +# coding: utf8 +from __future__ import unicode_literals + + +# These exceptions are used to add NORM values based on a token's ORTH value. +# Individual languages can also add their own exceptions and overwrite them - +# for example, British vs. American spelling in English. + +# Norms are only set if no alternative is provided in the tokenizer exceptions. +# Note that this does not change any other token attributes. Its main purpose +# is to normalise the word representations so that equivalent tokens receive +# similar representations. For example: $ and € are very different, but they're +# both currency symbols. By normalising currency symbols to $, all symbols are +# seen as similar, no matter how common they are in the training data. + + +BASE_NORMS = { + "'s": "'s", + "'S": "'s", + "’s": "'s", + "’S": "'s", + "’": "'", + "‘": "'", + "´": "'", + "`": "'", + "”": '"', + "“": '"', + "''": '"', + "``": '"', + "´´": '"', + "„": '"', + "»": '"', + "«": '"', + "…": "...", + "—": "-", + "–": "-", + "--": "-", + "---": "-", + "€": "$", + "£": "$", + "¥": "$", + "฿": "$", + "US$": "$", + "C$": "$", + "A$": "$" +} diff --git a/spacy/lang/pl/__init__.py b/spacy/lang/pl/__init__.py index 9fad81899..38a240598 100644 --- a/spacy/lang/pl/__init__.py +++ b/spacy/lang/pl/__init__.py @@ -1,23 +1,28 @@ # coding: utf8 from __future__ import unicode_literals +from .tokenizer_exceptions import TOKENIZER_EXCEPTIONS from .stop_words import STOP_WORDS from ..tokenizer_exceptions import BASE_EXCEPTIONS +from ..norm_exceptions import BASE_NORMS from ...language import Language -from ...attrs import LANG -from ...util import update_exc +from ...attrs import LANG, NORM +from ...util import update_exc, add_lookups + + +class PolishDefaults(Language.Defaults): + lex_attr_getters = dict(Language.Defaults.lex_attr_getters) + lex_attr_getters[LANG] = lambda text: 'pl' + lex_attr_getters[NORM] = add_lookups(Language.Defaults.lex_attr_getters[NORM], BASE_NORMS) + + tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) + stop_words = set(STOP_WORDS) class Polish(Language): lang = 'pl' - - class Defaults(Language.Defaults): - lex_attr_getters = dict(Language.Defaults.lex_attr_getters) - lex_attr_getters[LANG] = lambda text: 'pl' - - tokenizer_exceptions = update_exc(BASE_EXCEPTIONS) - stop_words = set(STOP_WORDS) + Defaults = PolishDefaults __all__ = ['Polish'] diff --git a/spacy/lang/pl/tokenizer_exceptions.py b/spacy/lang/pl/tokenizer_exceptions.py new file mode 100644 index 000000000..4dffb6209 --- /dev/null +++ b/spacy/lang/pl/tokenizer_exceptions.py @@ -0,0 +1,23 @@ +# encoding: utf8 +from __future__ import unicode_literals + +from ..symbols import ORTH, LEMMA, POS + + +_exc = {} + +for exc_data in [ + {ORTH: "m.in.", LEMMA: "między innymi", POS: ADV}, + {ORTH: "inż.", LEMMA: "inżynier", POS: NOUN}, + {ORTH: "mgr.", LEMMA: "magister", POS: NOUN}, + {ORTH: "tzn.", LEMMA: "to znaczy", POS: ADV}, + {ORTH: "tj.", LEMMA: "to jest", POS: ADV}, + {ORTH: "tzw.", LEMMA: "tak zwany", POS: ADJ}]: + _exc[exc_data[ORTH]] = [dict(exc_data)], + +for orth in [ + "w.", "r."]: + _exc[orth] = [{ORTH: orth}] + + +TOKENIZER_EXCEPTIONS = dict(_exc) diff --git a/spacy/lang/pt/__init__.py b/spacy/lang/pt/__init__.py index 314d05184..67539034d 100644 --- a/spacy/lang/pt/__init__.py +++ b/spacy/lang/pt/__init__.py @@ -7,26 +7,30 @@ from .lex_attrs import LEX_ATTRS from .lemmatizer import LOOKUP from ..tokenizer_exceptions import BASE_EXCEPTIONS +from ..norm_exceptions import BASE_NORMS from ...language import Language from ...lemmatizerlookup import Lemmatizer -from ...attrs import LANG -from ...util import update_exc +from ...attrs import LANG, NORM +from ...util import update_exc, add_lookups + + +class PortugueseDefaults(Language.Defaults): + lex_attr_getters = dict(Language.Defaults.lex_attr_getters) + lex_attr_getters[LANG] = lambda text: 'pt' + lex_attr_getters[NORM] = add_lookups(Language.Defaults.lex_attr_getters[NORM], BASE_NORMS) + lex_attr_getters.update(LEX_ATTRS) + + tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) + stop_words = set(STOP_WORDS) + + @classmethod + def create_lemmatizer(cls, nlp=None): + return Lemmatizer(LOOKUP) class Portuguese(Language): lang = 'pt' - - class Defaults(Language.Defaults): - lex_attr_getters = dict(Language.Defaults.lex_attr_getters) - lex_attr_getters[LANG] = lambda text: 'pt' - lex_attr_getters.update(LEX_ATTRS) - - tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) - stop_words = set(STOP_WORDS) - - @classmethod - def create_lemmatizer(cls, nlp=None): - return Lemmatizer(LOOKUP) + Defaults = PortugueseDefaults __all__ = ['Portuguese'] diff --git a/spacy/lang/punctuation.py b/spacy/lang/punctuation.py index 74bb28f5f..680f5cff0 100644 --- a/spacy/lang/punctuation.py +++ b/spacy/lang/punctuation.py @@ -2,15 +2,16 @@ from __future__ import unicode_literals from .char_classes import LIST_PUNCT, LIST_ELLIPSES, LIST_QUOTES, LIST_CURRENCY -from .char_classes import ALPHA_LOWER, ALPHA_UPPER, ALPHA, HYPHENS, QUOTES -from .char_classes import CURRENCY, UNITS +from .char_classes import LIST_ICONS, ALPHA_LOWER, ALPHA_UPPER, ALPHA, HYPHENS +from .char_classes import QUOTES, CURRENCY, UNITS _prefixes = (['§', '%', '=', r'\+'] + LIST_PUNCT + LIST_ELLIPSES + LIST_QUOTES + - LIST_CURRENCY) + LIST_CURRENCY + LIST_ICONS) -_suffixes = (["'s", "'S", "’s", "’S"] + LIST_PUNCT + LIST_ELLIPSES + LIST_QUOTES + +_suffixes = (LIST_PUNCT + LIST_ELLIPSES + LIST_QUOTES + LIST_ICONS + + ["'s", "'S", "’s", "’S"] + [r'(?<=[0-9])\+', r'(?<=°[FfCcKk])\.', r'(?<=[0-9])(?:{})'.format(CURRENCY), @@ -19,7 +20,7 @@ _suffixes = (["'s", "'S", "’s", "’S"] + LIST_PUNCT + LIST_ELLIPSES + LIST_QU r'(?<=[{a}][{a}])\.'.format(a=ALPHA_UPPER)]) -_infixes = (LIST_ELLIPSES + +_infixes = (LIST_ELLIPSES + LIST_ICONS + [r'(?<=[0-9])[+\-\*^](?=[0-9-])', r'(?<=[{}])\.(?=[{}])'.format(ALPHA_LOWER, ALPHA_UPPER), r'(?<=[{a}]),(?=[{a}])'.format(a=ALPHA), diff --git a/spacy/lang/sv/__init__.py b/spacy/lang/sv/__init__.py index b16e1befc..2d3a640c5 100644 --- a/spacy/lang/sv/__init__.py +++ b/spacy/lang/sv/__init__.py @@ -7,25 +7,29 @@ from .morph_rules import MORPH_RULES from .lemmatizer import LEMMA_RULES, LOOKUP from ..tokenizer_exceptions import BASE_EXCEPTIONS +from ..norm_exceptions import BASE_NORMS from ...language import Language from ...lemmatizerlookup import Lemmatizer -from ...attrs import LANG -from ...util import update_exc +from ...attrs import LANG, NORM +from ...util import update_exc, add_lookups + + +class SwedishDefaults(Language.Defaults): + lex_attr_getters = dict(Language.Defaults.lex_attr_getters) + lex_attr_getters[LANG] = lambda text: 'sv' + lex_attr_getters[NORM] = add_lookups(Language.Defaults.lex_attr_getters[NORM], BASE_NORMS) + + tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) + stop_words = set(STOP_WORDS) + + @classmethod + def create_lemmatizer(cls, nlp=None): + return Lemmatizer(LOOKUP) class Swedish(Language): lang = 'sv' - - class Defaults(Language.Defaults): - lex_attr_getters = dict(Language.Defaults.lex_attr_getters) - lex_attr_getters[LANG] = lambda text: 'sv' - - tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) - stop_words = set(STOP_WORDS) - - @classmethod - def create_lemmatizer(cls, nlp=None): - return Lemmatizer(LOOKUP) + Defaults = SwedishDefaults __all__ = ['Swedish'] diff --git a/spacy/lang/xx/__init__.py b/spacy/lang/xx/__init__.py new file mode 100644 index 000000000..dc63ee33f --- /dev/null +++ b/spacy/lang/xx/__init__.py @@ -0,0 +1,28 @@ +# coding: utf8 +from __future__ import unicode_literals + + +from ..tokenizer_exceptions import BASE_EXCEPTIONS +from ..norm_exceptions import BASE_NORMS +from ...language import Language +from ...attrs import LANG, NORM +from ...util import update_exc, add_lookups + + +class MultiLanguageDefaults(Language.Defaults): + lex_attr_getters = dict(Language.Defaults.lex_attr_getters) + lex_attr_getters[LANG] = lambda text: 'xx' + lex_attr_getters[NORM] = add_lookups(Language.Defaults.lex_attr_getters[NORM], BASE_NORMS) + + tokenizer_exceptions = update_exc(BASE_EXCEPTIONS) + + +class MultiLanguage(Language): + """Language class to be used for models that support multiple languages. + This module allows models to specify their language ID as 'xx'. + """ + lang = 'xx' + Defaults = MultiLanguageDefaults + + +__all__ = ['MultiLanguage'] diff --git a/spacy/lang/zh/__init__.py b/spacy/lang/zh/__init__.py index d63323b4e..3f68336f8 100644 --- a/spacy/lang/zh/__init__.py +++ b/spacy/lang/zh/__init__.py @@ -15,6 +15,7 @@ class Chinese(Language): raise ImportError("The Chinese tokenizer requires the Jieba library: " "https://github.com/fxsjy/jieba") words = list(jieba.cut(text, cut_all=True)) + words=[x for x in words if x] return Doc(self.vocab, words=words, spaces=[False]*len(words)) diff --git a/spacy/language.py b/spacy/language.py index 228225404..6d97f41fe 100644 --- a/spacy/language.py +++ b/spacy/language.py @@ -6,23 +6,34 @@ import dill import numpy from thinc.neural import Model from thinc.neural.ops import NumpyOps, CupyOps +from thinc.neural.optimizers import Adam, SGD +import random +import ujson +from collections import OrderedDict +import itertools from .tokenizer import Tokenizer from .vocab import Vocab from .tagger import Tagger from .lemmatizer import Lemmatizer -from .train import Trainer from .syntax.parser import get_templates -from .syntax.nonproj import PseudoProjectivity +from .syntax import nonproj + from .pipeline import NeuralDependencyParser, EntityRecognizer from .pipeline import TokenVectorEncoder, NeuralTagger, NeuralEntityRecognizer -from .compat import json_dumps +from .pipeline import NeuralLabeller +from .pipeline import SimilarityHook +from .pipeline import TextCategorizer +from . import about + +from .compat import json_dumps, izip from .attrs import IS_STOP from .lang.punctuation import TOKENIZER_PREFIXES, TOKENIZER_SUFFIXES, TOKENIZER_INFIXES from .lang.tokenizer_exceptions import TOKEN_MATCH from .lang.tag_map import TAG_MAP from .lang.lex_attrs import LEX_ATTRS from . import util +from .scorer import Scorer class BaseDefaults(object): @@ -80,21 +91,35 @@ class BaseDefaults(object): return NeuralEntityRecognizer(nlp.vocab, **cfg) @classmethod - def create_pipeline(cls, nlp=None): + def create_pipeline(cls, nlp=None, disable=tuple()): meta = nlp.meta if nlp is not None else {} # Resolve strings, like "cnn", "lstm", etc pipeline = [] for entry in cls.pipeline: + if entry in disable or getattr(entry, 'name', entry) in disable: + continue factory = cls.Defaults.factories[entry] pipeline.append(factory(nlp, **meta.get(entry, {}))) return pipeline factories = { 'make_doc': create_tokenizer, - 'token_vectors': lambda nlp, **cfg: TokenVectorEncoder(nlp.vocab, **cfg), - 'tags': lambda nlp, **cfg: NeuralTagger(nlp.vocab, **cfg), - 'dependencies': lambda nlp, **cfg: NeuralDependencyParser(nlp.vocab, **cfg), - 'entities': lambda nlp, **cfg: NeuralEntityRecognizer(nlp.vocab, **cfg), + 'tensorizer': lambda nlp, **cfg: [TokenVectorEncoder(nlp.vocab, **cfg)], + 'tagger': lambda nlp, **cfg: [NeuralTagger(nlp.vocab, **cfg)], + 'parser': lambda nlp, **cfg: [ + NeuralDependencyParser(nlp.vocab, **cfg), + nonproj.deprojectivize], + 'ner': lambda nlp, **cfg: [NeuralEntityRecognizer(nlp.vocab, **cfg)], + 'similarity': lambda nlp, **cfg: [SimilarityHook(nlp.vocab, **cfg)], + 'textcat': lambda nlp, **cfg: [TextCategorizer(nlp.vocab, **cfg)], + # Temporary compatibility -- delete after pivot + 'token_vectors': lambda nlp, **cfg: [TokenVectorEncoder(nlp.vocab, **cfg)], + 'tags': lambda nlp, **cfg: [NeuralTagger(nlp.vocab, **cfg)], + 'dependencies': lambda nlp, **cfg: [ + NeuralDependencyParser(nlp.vocab, **cfg), + nonproj.deprojectivize, + ], + 'entities': lambda nlp, **cfg: [NeuralEntityRecognizer(nlp.vocab, **cfg)], } token_match = TOKEN_MATCH @@ -112,19 +137,39 @@ class BaseDefaults(object): lemma_index = {} morph_rules = {} lex_attr_getters = LEX_ATTRS + syntax_iterators = {} class Language(object): - """ - A text-processing pipeline. Usually you'll load this once per process, and - pass the instance around your program. + """A text-processing pipeline. Usually you'll load this once per process, + and pass the instance around your application. + + Defaults (class): Settings, data and factory methods for creating the `nlp` + object and processing pipeline. + lang (unicode): Two-letter language ID, i.e. ISO code. """ Defaults = BaseDefaults lang = None - def __init__(self, vocab=True, make_doc=True, pipeline=None, meta={}): - self.meta = dict(meta) + def __init__(self, vocab=True, make_doc=True, pipeline=None, + meta={}, disable=tuple(), **kwargs): + """Initialise a Language object. + vocab (Vocab): A `Vocab` object. If `True`, a vocab is created via + `Language.Defaults.create_vocab`. + make_doc (callable): A function that takes text and returns a `Doc` + object. Usually a `Tokenizer`. + pipeline (list): A list of annotation processes or IDs of annotation, + processes, e.g. a `Tagger` object, or `'tagger'`. IDs are looked + up in `Language.Defaults.factories`. + disable (list): A list of component names to exclude from the pipeline. + The disable list has priority over the pipeline list -- if the same + string occurs in both, the component is not loaded. + meta (dict): Custom meta data for the Language class. Is written to by + models to add model meta data. + RETURNS (Language): The newly constructed object. + """ + self._meta = dict(meta) if vocab is True: factory = self.Defaults.create_vocab vocab = factory(self, **meta.get('vocab', {})) @@ -132,11 +177,15 @@ class Language(object): if make_doc is True: factory = self.Defaults.create_tokenizer make_doc = factory(self, **meta.get('tokenizer', {})) - self.make_doc = make_doc + self.tokenizer = make_doc if pipeline is True: - self.pipeline = self.Defaults.create_pipeline(self) + self.pipeline = self.Defaults.create_pipeline(self, disable) elif pipeline: - self.pipeline = list(pipeline) + # Careful not to do getattr(p, 'name', None) here + # If we had disable=[None], we'd disable everything! + self.pipeline = [p for p in pipeline + if p not in disable + and getattr(p, 'name', p) not in disable] # Resolve strings, like "cnn", "lstm", etc for i, entry in enumerate(self.pipeline): if entry in self.Defaults.factories: @@ -144,82 +193,224 @@ class Language(object): self.pipeline[i] = factory(self, **meta.get(entry, {})) else: self.pipeline = [] + flat_list = [] + for pipe in self.pipeline: + if isinstance(pipe, list): + flat_list.extend(pipe) + else: + flat_list.append(pipe) + self.pipeline = flat_list - def __call__(self, text, state=None, **disabled): - """ - Apply the pipeline to some text. The text can span multiple sentences, - and can contain arbtrary whitespace. Alignment into the original string + @property + def meta(self): + self._meta.setdefault('lang', self.vocab.lang) + self._meta.setdefault('name', '') + self._meta.setdefault('version', '0.0.0') + self._meta.setdefault('spacy_version', about.__version__) + self._meta.setdefault('description', '') + self._meta.setdefault('author', '') + self._meta.setdefault('email', '') + self._meta.setdefault('url', '') + self._meta.setdefault('license', '') + pipeline = [] + for component in self.pipeline: + if hasattr(component, 'name'): + pipeline.append(component.name) + self._meta['pipeline'] = pipeline + return self._meta + + @meta.setter + def meta(self, value): + self._meta = value + + # Conveniences to access pipeline components + @property + def tensorizer(self): + return self.get_component('tensorizer') + + @property + def tagger(self): + return self.get_component('tagger') + + @property + def parser(self): + return self.get_component('parser') + + @property + def entity(self): + return self.get_component('ner') + + @property + def matcher(self): + return self.get_component('matcher') + + def get_component(self, name): + if self.pipeline in (True, None): + return None + for proc in self.pipeline: + if hasattr(proc, 'name') and proc.name.endswith(name): + return proc + return None + + def __call__(self, text, disable=[]): + """'Apply the pipeline to some text. The text can span multiple sentences, + and can contain arbtrary whitespace. Alignment into the original string is preserved. - Args: - text (unicode): The text to be processed. - state: Arbitrary + text (unicode): The text to be processed. + disable (list): Names of the pipeline components to disable. + RETURNS (Doc): A container for accessing the annotations. - Returns: - doc (Doc): A container for accessing the annotations. - - Example: - >>> from spacy.en import English - >>> nlp = English() + EXAMPLE: >>> tokens = nlp('An example sentence. Another example sentence.') - >>> tokens[0].orth_, tokens[0].head.tag_ + >>> tokens[0].text, tokens[0].head.tag_ ('An', 'NN') """ doc = self.make_doc(text) for proc in self.pipeline: name = getattr(proc, 'name', None) - if name in disabled and not disabled[name]: + if name in disable: continue - state = proc(doc, state=state) + doc = proc(doc) return doc - def update(self, docs, golds, state=None, drop=0., sgd=None): + def make_doc(self, text): + return self.tokenizer(text) + + def update(self, docs, golds, drop=0., sgd=None, losses=None, + update_tensors=False): + """Update the models in the pipeline. + + docs (iterable): A batch of `Doc` objects. + golds (iterable): A batch of `GoldParse` objects. + drop (float): The droput rate. + sgd (callable): An optimizer. + RETURNS (dict): Results from the update. + + EXAMPLE: + >>> with nlp.begin_training(gold, use_gpu=True) as (trainer, optimizer): + >>> for epoch in trainer.epochs(gold): + >>> for docs, golds in epoch: + >>> state = nlp.update(docs, golds, sgd=optimizer) + """ + if len(docs) != len(golds): + raise IndexError("Update expects same number of docs and golds " + "Got: %d, %d" % (len(docs), len(golds))) + if len(docs) == 0: + return + tok2vec = self.pipeline[0] + feats = tok2vec.doc2feats(docs) grads = {} def get_grads(W, dW, key=None): grads[key] = (W, dW) - state = {} if state is None else state - for process in self.pipeline: - if hasattr(process, 'update'): - state = process.update(docs, golds, - state=state, - drop=drop, - sgd=get_grads) - else: - process(docs, state=state) - if sgd is not None: - for key, (W, dW) in grads.items(): - # TODO: Unhack this when thinc improves - if isinstance(W, numpy.ndarray): - sgd.ops = NumpyOps() - else: - sgd.ops = CupyOps() - sgd(W, dW, key=key) - return state + pipes = list(self.pipeline[1:]) + random.shuffle(pipes) + for proc in pipes: + if not hasattr(proc, 'update'): + continue + tokvecses, bp_tokvecses = tok2vec.model.begin_update(feats, drop=drop) + d_tokvecses = proc.update((docs, tokvecses), golds, + drop=drop, sgd=get_grads, losses=losses) + if update_tensors and d_tokvecses is not None: + bp_tokvecses(d_tokvecses, sgd=sgd) + for key, (W, dW) in grads.items(): + sgd(W, dW, key=key) + # Clear the tensor variable, to free GPU memory. + # If we don't do this, the memory leak gets pretty + # bad, because we may be holding part of a batch. + for doc in docs: + doc.tensor = None - @contextmanager - def begin_training(self, gold_tuples, **cfg): + def preprocess_gold(self, docs_golds): + """Can be called before training to pre-process gold data. By default, + it handles nonprojectivity and adds missing tags to the tag map. + + docs_golds (iterable): Tuples of `Doc` and `GoldParse` objects. + YIELDS (tuple): Tuples of preprocessed `Doc` and `GoldParse` objects. + """ + for proc in self.pipeline: + if hasattr(proc, 'preprocess_gold'): + docs_golds = proc.preprocess_gold(docs_golds) + for doc, gold in docs_golds: + yield doc, gold + + def begin_training(self, get_gold_tuples, **cfg): + """Allocate models, pre-process training data and acquire a trainer and + optimizer. Used as a contextmanager. + + gold_tuples (iterable): Gold-standard training data. + **cfg: Config parameters. + YIELDS (tuple): A trainer and an optimizer. + + EXAMPLE: + >>> with nlp.begin_training(gold, use_gpu=True) as (trainer, optimizer): + >>> for epoch in trainer.epochs(gold): + >>> for docs, golds in epoch: + >>> state = nlp.update(docs, golds, sgd=optimizer) + """ + if self.parser: + self.pipeline.append(NeuralLabeller(self.vocab)) # Populate vocab - for _, annots_brackets in gold_tuples: + for _, annots_brackets in get_gold_tuples(): for annots, _ in annots_brackets: for word in annots[1]: _ = self.vocab[word] - # Handle crossing dependencies - gold_tuples = PseudoProjectivity.preprocess_training_data(gold_tuples) contexts = [] - if cfg.get('use_gpu'): + if cfg.get('device', -1) >= 0: + import cupy.cuda.device + device = cupy.cuda.device.Device(cfg['device']) + device.use() Model.ops = CupyOps() Model.Ops = CupyOps - print("Use GPU") + else: + device = None for proc in self.pipeline: if hasattr(proc, 'begin_training'): - context = proc.begin_training(gold_tuples, + context = proc.begin_training(get_gold_tuples(), pipeline=self.pipeline) contexts.append(context) - trainer = Trainer(self, gold_tuples, **cfg) - yield trainer, trainer.optimizer + learn_rate = util.env_opt('learn_rate', 0.001) + beta1 = util.env_opt('optimizer_B1', 0.9) + beta2 = util.env_opt('optimizer_B2', 0.999) + eps = util.env_opt('optimizer_eps', 1e-08) + L2 = util.env_opt('L2_penalty', 1e-6) + max_grad_norm = util.env_opt('grad_norm_clip', 1.) + optimizer = Adam(Model.ops, learn_rate, L2=L2, beta1=beta1, + beta2=beta2, eps=eps) + optimizer.max_grad_norm = max_grad_norm + optimizer.device = device + return optimizer + + def evaluate(self, docs_golds): + scorer = Scorer() + docs, golds = zip(*docs_golds) + docs = list(docs) + golds = list(golds) + for pipe in self.pipeline: + if not hasattr(pipe, 'pipe'): + for doc in docs: + pipe(doc) + else: + docs = list(pipe.pipe(docs)) + assert len(docs) == len(golds) + for doc, gold in zip(docs, golds): + scorer.score(doc, gold) + doc.tensor = None + return scorer @contextmanager def use_params(self, params, **cfg): + """Replace weights of models in the pipeline with those provided in the + params dictionary. Can be used as a contextmanager, in which case, + models go back to their original weights after the block. + + params (dict): A dictionary of parameters keyed by model ID. + **cfg: Config parameters. + + EXAMPLE: + >>> with nlp.use_params(optimizer.averages): + >>> nlp.to_disk('/tmp/checkpoint') + """ contexts = [pipe.use_params(params) for pipe in self.pipeline if hasattr(pipe, 'use_params')] # TODO: Having trouble with contextlib @@ -236,98 +427,149 @@ class Language(object): except StopIteration: pass - def pipe(self, texts, n_threads=2, batch_size=1000, **disabled): - """ - Process texts as a stream, and yield Doc objects in order. + def pipe(self, texts, tuples=False, n_threads=2, batch_size=1000, disable=[]): + """Process texts as a stream, and yield `Doc` objects in order. Supports + GIL-free multi-threading. - Supports GIL-free multi-threading. + texts (iterator): A sequence of texts to process. + n_threads (int): The number of worker threads to use. If -1, OpenMP will + decide how many to use at run time. Default is 2. + batch_size (int): The number of texts to buffer. + disable (list): Names of the pipeline components to disable. + YIELDS (Doc): Documents in the order of the original text. - Arguments: - texts (iterator) - tag (bool) - parse (bool) - entity (bool) + EXAMPLE: + >>> texts = [u'One document.', u'...', u'Lots of documents'] + >>> for doc in nlp.pipe(texts, batch_size=50, n_threads=4): + >>> assert doc.is_parsed """ - #stream = ((self.make_doc(text), None) for text in texts) - stream = ((doc, {}) for doc in texts) + if tuples: + text_context1, text_context2 = itertools.tee(texts) + texts = (tc[0] for tc in text_context1) + contexts = (tc[1] for tc in text_context2) + docs = self.pipe(texts, n_threads=n_threads, batch_size=batch_size, + disable=disable) + for doc, context in izip(docs, contexts): + yield (doc, context) + return + docs = (self.make_doc(text) for text in texts) for proc in self.pipeline: name = getattr(proc, 'name', None) - if name in disabled and not disabled[name]: + if name in disable: continue - if hasattr(proc, 'pipe'): - stream = proc.pipe(stream, n_threads=n_threads, batch_size=batch_size) + docs = proc.pipe(docs, n_threads=n_threads, batch_size=batch_size) else: - stream = (proc(doc, state) for doc, state in stream) - for doc, state in stream: + # Apply the function, but yield the doc + docs = _pipe(proc, docs) + for doc in docs: yield doc - def to_disk(self, path, **exclude): - """Save the current state to a directory. + def to_disk(self, path, disable=tuple()): + """Save the current state to a directory. If a model is loaded, this + will include the model. - Args: - path: A path to a directory, which will be created if it doesn't - exist. Paths may be either strings or pathlib.Path-like - objects. - **exclude: Prevent named attributes from being saved. + path (unicode or Path): A path to a directory, which will be created if + it doesn't exist. Paths may be either strings or `Path`-like objects. + disable (list): Names of pipeline components to disable and prevent + from being saved. + + EXAMPLE: + >>> nlp.to_disk('/path/to/models') """ path = util.ensure_path(path) - if not path.exists(): - path.mkdir() - if not path.is_dir(): - raise IOError("Output path must be a directory") - props = {} - for name, value in self.__dict__.items(): - if name in exclude: + serializers = OrderedDict(( + ('vocab', lambda p: self.vocab.to_disk(p)), + ('tokenizer', lambda p: self.tokenizer.to_disk(p, vocab=False)), + ('meta.json', lambda p: p.open('w').write(json_dumps(self.meta))) + )) + for proc in self.pipeline: + if not hasattr(proc, 'name'): continue - if hasattr(value, 'to_disk'): - value.to_disk(path / name) - else: - props[name] = value - with (path / 'props.pickle').open('wb') as file_: - dill.dump(props, file_) + if proc.name in disable: + continue + if not hasattr(proc, 'to_disk'): + continue + serializers[proc.name] = lambda p, proc=proc: proc.to_disk(p, vocab=False) + util.to_disk(path, serializers, {p: False for p in disable}) - def from_disk(self, path, **exclude): - """Load the current state from a directory. + def from_disk(self, path, disable=tuple()): + """Loads state from a directory. Modifies the object in place and + returns it. If the saved `Language` object contains a model, the + model will be loaded. - Args: - path: A path to a directory. Paths may be either strings or - pathlib.Path-like objects. - **exclude: Prevent named attributes from being saved. + path (unicode or Path): A path to a directory. Paths may be either + strings or `Path`-like objects. + disable (list): Names of the pipeline components to disable. + RETURNS (Language): The modified `Language` object. + + EXAMPLE: + >>> from spacy.language import Language + >>> nlp = Language().from_disk('/path/to/models') """ path = util.ensure_path(path) - for name in path.iterdir(): - if name not in exclude and hasattr(self, str(name)): - getattr(self, name).from_disk(path / name) - with (path / 'props.pickle').open('rb') as file_: - bytes_data = file_.read() - self.from_bytes(bytes_data, **exclude) + deserializers = OrderedDict(( + ('vocab', lambda p: self.vocab.from_disk(p)), + ('tokenizer', lambda p: self.tokenizer.from_disk(p, vocab=False)), + ('meta.json', lambda p: p.open('w').write(json_dumps(self.meta))) + )) + for proc in self.pipeline: + if not hasattr(proc, 'name'): + continue + if proc.name in disable: + continue + if not hasattr(proc, 'to_disk'): + continue + deserializers[proc.name] = lambda p, proc=proc: proc.from_disk(p, vocab=False) + exclude = {p: False for p in disable} + if not (path / 'vocab').exists(): + exclude['vocab'] = True + util.from_disk(path, deserializers, exclude) return self - def to_bytes(self, **exclude): + def to_bytes(self, disable=[]): """Serialize the current state to a binary string. - Args: - path: A path to a directory. Paths may be either strings or - pathlib.Path-like objects. - **exclude: Prevent named attributes from being serialized. + disable (list): Nameds of pipeline components to disable and prevent + from being serialized. + RETURNS (bytes): The serialized form of the `Language` object. """ - props = dict(self.__dict__) - for key in exclude: - if key in props: - props.pop(key) - return dill.dumps(props, -1) + serializers = OrderedDict(( + ('vocab', lambda: self.vocab.to_bytes()), + ('tokenizer', lambda: self.tokenizer.to_bytes(vocab=False)), + ('meta', lambda: ujson.dumps(self.meta)) + )) + for i, proc in enumerate(self.pipeline): + if getattr(proc, 'name', None) in disable: + continue + if not hasattr(proc, 'to_bytes'): + continue + serializers[i] = lambda proc=proc: proc.to_bytes(vocab=False) + return util.to_bytes(serializers, {}) - def from_bytes(self, bytes_data, **exclude): + def from_bytes(self, bytes_data, disable=[]): """Load state from a binary string. - Args: - bytes_data (bytes): The data to load from. - **exclude: Prevent named attributes from being loaded. + bytes_data (bytes): The data to load from. + disable (list): Names of the pipeline components to disable. + RETURNS (Language): The `Language` object. """ - props = dill.loads(bytes_data) - for key, value in props.items(): - if key not in exclude: - setattr(self, key, value) + deserializers = OrderedDict(( + ('vocab', lambda b: self.vocab.from_bytes(b)), + ('tokenizer', lambda b: self.tokenizer.from_bytes(b, vocab=False)), + ('meta', lambda b: self.meta.update(ujson.loads(b))) + )) + for i, proc in enumerate(self.pipeline): + if getattr(proc, 'name', None) in disable: + continue + if not hasattr(proc, 'from_bytes'): + continue + deserializers[i] = lambda b, proc=proc: proc.from_bytes(b, vocab=False) + msg = util.from_bytes(bytes_data, deserializers, {}) return self + +def _pipe(func, docs): + for doc in docs: + func(doc) + yield doc diff --git a/spacy/lexeme.pxd b/spacy/lexeme.pxd index b058c66e3..922d97737 100644 --- a/spacy/lexeme.pxd +++ b/spacy/lexeme.pxd @@ -27,7 +27,7 @@ cdef class Lexeme: cdef inline SerializedLexemeC c_to_bytes(const LexemeC* lex) nogil: cdef SerializedLexemeC lex_data buff = &lex.flags - end = &lex.l2_norm + sizeof(lex.l2_norm) + end = &lex.sentiment + sizeof(lex.sentiment) for i in range(sizeof(lex_data.data)): lex_data.data[i] = buff[i] return lex_data @@ -35,7 +35,7 @@ cdef class Lexeme: @staticmethod cdef inline void c_from_bytes(LexemeC* lex, SerializedLexemeC lex_data) nogil: buff = &lex.flags - end = &lex.l2_norm + sizeof(lex.l2_norm) + end = &lex.sentiment + sizeof(lex.sentiment) for i in range(sizeof(lex_data.data)): buff[i] = lex_data.data[i] diff --git a/spacy/lexeme.pyx b/spacy/lexeme.pyx index effffbac8..bcd84d184 100644 --- a/spacy/lexeme.pyx +++ b/spacy/lexeme.pyx @@ -30,19 +30,16 @@ memset(&EMPTY_LEXEME, 0, sizeof(LexemeC)) cdef class Lexeme: - """ - An entry in the vocabulary. A Lexeme has no string context --- it's a + """An entry in the vocabulary. A `Lexeme` has no string context – it's a word-type, as opposed to a word token. It therefore has no part-of-speech tag, dependency parse, or lemma (lemmatization depends on the part-of-speech tag). """ - def __init__(self, Vocab vocab, int orth): - """ - Create a Lexeme object. + def __init__(self, Vocab vocab, attr_t orth): + """Create a Lexeme object. - Arguments: - vocab (Vocab): The parent vocabulary - orth (int): The orth id of the lexeme. + vocab (Vocab): The parent vocabulary + orth (uint64): The orth id of the lexeme. Returns (Lexeme): The newly constructd object. """ self.vocab = vocab @@ -54,7 +51,7 @@ cdef class Lexeme: if isinstance(other, Lexeme): a = self.orth b = other.orth - elif isinstance(other, int): + elif isinstance(other, long): a = self.orth b = other elif isinstance(other, str): @@ -82,35 +79,28 @@ cdef class Lexeme: return self.c.orth def set_flag(self, attr_id_t flag_id, bint value): - """ - Change the value of a boolean flag. + """Change the value of a boolean flag. - Arguments: - flag_id (int): The attribute ID of the flag to set. - value (bool): The new value of the flag. + flag_id (int): The attribute ID of the flag to set. + value (bool): The new value of the flag. """ Lexeme.c_set_flag(self.c, flag_id, value) def check_flag(self, attr_id_t flag_id): - """ - Check the value of a boolean flag. + """Check the value of a boolean flag. - Arguments: - flag_id (int): The attribute ID of the flag to query. - Returns (bool): The value of the flag. + flag_id (int): The attribute ID of the flag to query. + RETURNS (bool): The value of the flag. """ return True if Lexeme.c_check_flag(self.c, flag_id) else False def similarity(self, other): - """ - Compute a semantic similarity estimate. Defaults to cosine over vectors. + """Compute a semantic similarity estimate. Defaults to cosine over + vectors. - Arguments: - other: - The object to compare with. By default, accepts Doc, Span, - Token and Lexeme objects. - Returns: - score (float): A scalar similarity score. Higher is more similar. + other (object): The object to compare with. By default, accepts `Doc`, + `Span`, `Token` and `Lexeme` objects. + RETURNS (float): A scalar similarity score. Higher is more similar. """ if self.vector_norm == 0 or other.vector_norm == 0: return 0.0 @@ -119,7 +109,7 @@ cdef class Lexeme: def to_bytes(self): lex_data = Lexeme.c_to_bytes(self.c) start = &self.c.flags - end = &self.c.l2_norm + sizeof(self.c.l2_norm) + end = &self.c.sentiment + sizeof(self.c.sentiment) assert (end-start) == sizeof(lex_data.data), (end-start, sizeof(lex_data.data)) byte_string = b'\0' * sizeof(lex_data.data) byte_chars = byte_string @@ -140,22 +130,29 @@ cdef class Lexeme: self.orth = self.c.orth property has_vector: + """A boolean value indicating whether a word vector is associated with + the object. + + RETURNS (bool): Whether a word vector is associated with the object. + """ def __get__(self): - cdef int i - for i in range(self.vocab.vectors_length): - if self.c.vector[i] != 0: - return True - else: - return False + return self.vocab.has_vector(self.c.orth) property vector_norm: - def __get__(self): - return self.c.l2_norm + """The L2 norm of the lexeme's vector representation. - def __set__(self, float value): - self.c.l2_norm = value + RETURNS (float): The L2 norm of the vector representation. + """ + def __get__(self): + vector = self.vector + return numpy.sqrt((vector**2).sum()) property vector: + """A real-valued meaning representation. + + RETURNS (numpy.ndarray[ndim=1, dtype='float32']): A 1D numpy array + representing the lexeme's semantics. + """ def __get__(self): cdef int length = self.vocab.vectors_length if length == 0: @@ -165,27 +162,16 @@ cdef class Lexeme: "model doesn't include word vectors. For more info, see " "the documentation: \n%s\n" % about.__docs_models__ ) - - vector_view = self.c.vector - return numpy.asarray(vector_view) + return self.vocab.get_vector(self.c.orth) def __set__(self, vector): assert len(vector) == self.vocab.vectors_length - cdef float value - cdef double norm = 0.0 - for i, value in enumerate(vector): - self.c.vector[i] = value - norm += value * value - self.c.l2_norm = sqrt(norm) + self.vocab.set_vector(self.c.orth, vector) property rank: def __get__(self): return self.c.id - property repvec: - def __get__(self): - raise AttributeError("lex.repvec has been renamed to lex.vector") - property sentiment: def __get__(self): return self.c.sentiment @@ -196,33 +182,41 @@ cdef class Lexeme: def __get__(self): return self.vocab.strings[self.c.orth] + property text: + """A unicode representation of the token text. + + RETURNS (unicode): The original verbatim text of the token. + """ + def __get__(self): + return self.orth_ + property lower: def __get__(self): return self.c.lower - def __set__(self, int x): self.c.lower = x + def __set__(self, attr_t x): self.c.lower = x property norm: def __get__(self): return self.c.norm - def __set__(self, int x): self.c.norm = x + def __set__(self, attr_t x): self.c.norm = x property shape: def __get__(self): return self.c.shape - def __set__(self, int x): self.c.shape = x + def __set__(self, attr_t x): self.c.shape = x property prefix: def __get__(self): return self.c.prefix - def __set__(self, int x): self.c.prefix = x + def __set__(self, attr_t x): self.c.prefix = x property suffix: def __get__(self): return self.c.suffix - def __set__(self, int x): self.c.suffix = x + def __set__(self, attr_t x): self.c.suffix = x property cluster: def __get__(self): return self.c.cluster - def __set__(self, int x): self.c.cluster = x + def __set__(self, attr_t x): self.c.cluster = x property lang: def __get__(self): return self.c.lang - def __set__(self, int x): self.c.lang = x + def __set__(self, attr_t x): self.c.lang = x property prob: def __get__(self): return self.c.prob @@ -230,27 +224,27 @@ cdef class Lexeme: property lower_: def __get__(self): return self.vocab.strings[self.c.lower] - def __set__(self, unicode x): self.c.lower = self.vocab.strings[x] + def __set__(self, unicode x): self.c.lower = self.vocab.strings.add(x) property norm_: def __get__(self): return self.vocab.strings[self.c.norm] - def __set__(self, unicode x): self.c.norm = self.vocab.strings[x] + def __set__(self, unicode x): self.c.norm = self.vocab.strings.add(x) property shape_: def __get__(self): return self.vocab.strings[self.c.shape] - def __set__(self, unicode x): self.c.shape = self.vocab.strings[x] + def __set__(self, unicode x): self.c.shape = self.vocab.strings.add(x) property prefix_: def __get__(self): return self.vocab.strings[self.c.prefix] - def __set__(self, unicode x): self.c.prefix = self.vocab.strings[x] + def __set__(self, unicode x): self.c.prefix = self.vocab.strings.add(x) property suffix_: def __get__(self): return self.vocab.strings[self.c.suffix] - def __set__(self, unicode x): self.c.suffix = self.vocab.strings[x] + def __set__(self, unicode x): self.c.suffix = self.vocab.strings.add(x) property lang_: def __get__(self): return self.vocab.strings[self.c.lang] - def __set__(self, unicode x): self.c.lang = self.vocab.strings[x] + def __set__(self, unicode x): self.c.lang = self.vocab.strings.add(x) property flags: def __get__(self): return self.c.flags @@ -258,7 +252,7 @@ cdef class Lexeme: property is_oov: def __get__(self): return Lexeme.c_check_flag(self.c, IS_OOV) - def __set__(self, bint x): Lexeme.c_set_flag(self.c, IS_OOV, x) + def __set__(self, attr_t x): Lexeme.c_set_flag(self.c, IS_OOV, x) property is_stop: def __get__(self): return Lexeme.c_check_flag(self.c, IS_STOP) @@ -308,7 +302,6 @@ cdef class Lexeme: def __get__(self): return Lexeme.c_check_flag(self.c, IS_RIGHT_PUNCT) def __set__(self, bint x): Lexeme.c_set_flag(self.c, IS_RIGHT_PUNCT, x) - property like_url: def __get__(self): return Lexeme.c_check_flag(self.c, LIKE_URL) def __set__(self, bint x): Lexeme.c_set_flag(self.c, LIKE_URL, x) diff --git a/spacy/matcher.pyx b/spacy/matcher.pyx index c9084c359..c75d23957 100644 --- a/spacy/matcher.pyx +++ b/spacy/matcher.pyx @@ -87,7 +87,7 @@ ctypedef TokenPatternC* TokenPatternC_ptr ctypedef pair[int, TokenPatternC_ptr] StateC -cdef TokenPatternC* init_pattern(Pool mem, attr_t entity_id, attr_t label, +cdef TokenPatternC* init_pattern(Pool mem, attr_t entity_id, object token_specs) except NULL: pattern = mem.alloc(len(token_specs) + 1, sizeof(TokenPatternC)) cdef int i @@ -99,15 +99,21 @@ cdef TokenPatternC* init_pattern(Pool mem, attr_t entity_id, attr_t label, pattern[i].attrs[j].attr = attr pattern[i].attrs[j].value = value i = len(token_specs) - pattern[i].attrs = mem.alloc(3, sizeof(AttrValueC)) + pattern[i].attrs = mem.alloc(2, sizeof(AttrValueC)) pattern[i].attrs[0].attr = ID pattern[i].attrs[0].value = entity_id - pattern[i].attrs[1].attr = ENT_TYPE - pattern[i].attrs[1].value = label pattern[i].nr_attr = 0 return pattern +cdef attr_t get_pattern_key(const TokenPatternC* pattern) except 0: + while pattern.nr_attr != 0: + pattern += 1 + id_attr = pattern[0].attrs[0] + assert id_attr.attr == ID + return id_attr.value + + cdef int get_action(const TokenPatternC* pattern, const TokenC* token) nogil: for attr in pattern.attrs[:pattern.nr_attr]: if get_token_attr(token, attr.attr) != attr.value: @@ -148,7 +154,7 @@ def _convert_strings(token_specs, string_store): if isinstance(attr, basestring): attr = attrs.IDS.get(attr.upper()) if isinstance(value, basestring): - value = string_store[value] + value = string_store.add(value) if isinstance(value, bool): value = int(value) if attr is not None: @@ -159,14 +165,14 @@ def _convert_strings(token_specs, string_store): def merge_phrase(matcher, doc, i, matches): - '''Callback to merge a phrase on match''' + """Callback to merge a phrase on match.""" ent_id, label, start, end = matches[i] span = doc[start : end] span.merge(ent_type=label, ent_id=ent_id) cdef class Matcher: - '''Match sequences of tokens, based on pattern rules.''' + """Match sequences of tokens, based on pattern rules.""" cdef Pool mem cdef vector[TokenPatternC*] patterns cdef readonly Vocab vocab @@ -175,37 +181,12 @@ cdef class Matcher: cdef public object _callbacks cdef public object _acceptors - @classmethod - def load(cls, path, vocab): - """ - Load the matcher and patterns from a file path. + def __init__(self, vocab): + """Create the Matcher. - Arguments: - path (Path): - Path to a JSON-formatted patterns file. - vocab (Vocab): - The vocabulary that the documents to match over will refer to. - Returns: - Matcher: The newly constructed object. - """ - if (path / 'gazetteer.json').exists(): - with (path / 'gazetteer.json').open('r', encoding='utf8') as file_: - patterns = ujson.load(file_) - else: - patterns = {} - return cls(vocab, patterns) - - def __init__(self, vocab, patterns={}): - """ - Create the Matcher. - - Arguments: - vocab (Vocab): - The vocabulary object, which must be shared with the documents - the matcher will operate on. - patterns (dict): Patterns to add to the matcher. - Returns: - The newly constructed object. + vocab (Vocab): The vocabulary object, which must be shared with the + documents the matcher will operate on. + RETURNS (Matcher): The newly constructed object. """ self._patterns = {} self._entities = {} @@ -213,144 +194,111 @@ cdef class Matcher: self._callbacks = {} self.vocab = vocab self.mem = Pool() - for entity_key, (etype, attrs, specs) in sorted(patterns.items()): - self.add_entity(entity_key, attrs) - for spec in specs: - self.add_pattern(entity_key, spec, label=etype) def __reduce__(self): return (self.__class__, (self.vocab, self._patterns), None, None) - property n_patterns: - def __get__(self): return self.patterns.size() + def __len__(self): + """Get the number of rules added to the matcher. Note that this only + returns the number of rules (identical with the number of IDs), not the + number of individual patterns. - def add_entity(self, entity_key, attrs=None, if_exists='raise', - acceptor=None, on_match=None): + RETURNS (int): The number of rules. """ - Add an entity to the matcher. + return len(self._patterns) - Arguments: - entity_key (unicode or int): - An ID for the entity. - attrs: - Attributes to associate with the Matcher. - if_exists ('raise', 'ignore' or 'update'): - Controls what happens if the entity ID already exists. Defaults to 'raise'. - acceptor: - Callback function to filter matches of the entity. - on_match: - Callback function to act on matches of the entity. - Returns: - None + def __contains__(self, key): + """Check whether the matcher contains rules for a match ID. + + key (unicode): The match ID. + RETURNS (bool): Whether the matcher contains rules for this match ID. """ - if if_exists not in ('raise', 'ignore', 'update'): - raise ValueError( - "Unexpected value for if_exists: %s.\n" - "Expected one of: ['raise', 'ignore', 'update']" % if_exists) - if attrs is None: - attrs = {} - entity_key = self.normalize_entity_key(entity_key) - if self.has_entity(entity_key): - if if_exists == 'raise': - raise KeyError( - "Tried to add entity %s. Entity exists, and if_exists='raise'.\n" - "Set if_exists='ignore' or if_exists='update', or check with " - "matcher.has_entity()") - elif if_exists == 'ignore': - return - self._entities[entity_key] = dict(attrs) - self._patterns.setdefault(entity_key, []) - self._acceptors[entity_key] = acceptor - self._callbacks[entity_key] = on_match + return len(self._patterns) - def add_pattern(self, entity_key, token_specs, label=""): + def add(self, key, on_match, *patterns): + """Add a match-rule to the matcher. + A match-rule consists of: an ID key, an on_match callback, and one or + more patterns. If the key exists, the patterns are appended to the + previous ones, and the previous on_match callback is replaced. The + `on_match` callback will receive the arguments `(matcher, doc, i, + matches)`. You can also set `on_match` to `None` to not perform any + actions. A pattern consists of one or more `token_specs`, where a + `token_spec` is a dictionary mapping attribute IDs to values. Token + descriptors can also include quantifiers. There are currently important + known problems with the quantifiers – see the docs. """ - Add a pattern to the matcher. + for pattern in patterns: + if len(pattern) == 0: + msg = ("Cannot add pattern for zero tokens to matcher.\n" + "key: {key}\n") + raise ValueError(msg.format(key=key)) + key = self._normalize_key(key) + self._patterns.setdefault(key, []) + self._callbacks[key] = on_match - Arguments: - entity_key (unicode or int): - An ID for the entity. - token_specs: - Description of the pattern to be matched. - label: - Label to assign to the matched pattern. Defaults to "". - Returns: - None + for pattern in patterns: + specs = _convert_strings(pattern, self.vocab.strings) + self.patterns.push_back(init_pattern(self.mem, key, specs)) + self._patterns[key].append(specs) + + def remove(self, key): + """Remove a rule from the matcher. A KeyError is raised if the key does + not exist. + + key (unicode): The ID of the match rule. """ - token_specs = list(token_specs) - if len(token_specs) == 0: - msg = ("Cannot add pattern for zero tokens to matcher.\n" - "entity_key: {entity_key}\n" - "label: {label}") - raise ValueError(msg.format(entity_key=entity_key, label=label)) - entity_key = self.normalize_entity_key(entity_key) - if not self.has_entity(entity_key): - self.add_entity(entity_key) - if isinstance(label, basestring): - label = self.vocab.strings[label] - elif label is None: - label = 0 - spec = _convert_strings(token_specs, self.vocab.strings) + key = self._normalize_key(key) + self._patterns.pop(key) + self._callbacks.pop(key) + cdef int i = 0 + while i < self.patterns.size(): + pattern_key = get_pattern_key(self.patterns.at(i)) + if pattern_key == key: + self.patterns.erase(self.patterns.begin()+i) + else: + i += 1 - self.patterns.push_back(init_pattern(self.mem, entity_key, label, spec)) - self._patterns[entity_key].append((label, token_specs)) + def has_key(self, key): + """Check whether the matcher has a rule with a given key. - def add(self, entity_key, label, attrs, specs, acceptor=None, on_match=None): - self.add_entity(entity_key, attrs=attrs, if_exists='update', - acceptor=acceptor, on_match=on_match) - for spec in specs: - self.add_pattern(entity_key, spec, label=label) - - def normalize_entity_key(self, entity_key): - if isinstance(entity_key, basestring): - return self.vocab.strings[entity_key] - else: - return entity_key - - def has_entity(self, entity_key): + key (string or int): The key to check. + RETURNS (bool): Whether the matcher has the rule. """ - Check whether the matcher has an entity. + key = self._normalize_key(key) + return key in self._patterns - Arguments: - entity_key (string or int): The entity key to check. - Returns: - bool: Whether the matcher has the entity. - """ - entity_key = self.normalize_entity_key(entity_key) - return entity_key in self._entities + def get(self, key, default=None): + """Retrieve the pattern stored for a key. - def get_entity(self, entity_key): + key (unicode or int): The key to retrieve. + RETURNS (tuple): The rule, as an (on_match, patterns) tuple. """ - Retrieve the attributes stored for an entity. + key = self._normalize_key(key) + if key not in self._patterns: + return default + return (self._callbacks[key], self._patterns[key]) - Arguments: - entity_key (unicode or int): The entity to retrieve. - Returns: - The entity attributes if present, otherwise None. - """ - entity_key = self.normalize_entity_key(entity_key) - if entity_key in self._entities: - return self._entities[entity_key] - else: - return None + def pipe(self, docs, batch_size=1000, n_threads=2): + """Match a stream of documents, yielding them in turn. - def __call__(self, Doc doc, acceptor=None): + docs (iterable): A stream of documents. + batch_size (int): The number of documents to accumulate into a working set. + n_threads (int): The number of threads with which to work on the buffer + in parallel, if the `Matcher` implementation supports multi-threading. + YIELDS (Doc): Documents, in order. """ - Find all token sequences matching the supplied patterns on the Doc. + for doc in docs: + self(doc) + yield doc - Arguments: - doc (Doc): - The document to match over. - Returns: - list - A list of (entity_key, label_id, start, end) tuples, - describing the matches. A match tuple describes a span doc[start:end]. - The label_id and entity_key are both integers. + def __call__(self, Doc doc): + """Find all token sequences matching the supplied patterns on the `Doc`. + + doc (Doc): The document to match over. + RETURNS (list): A list of `(key, label_id, start, end)` tuples, + describing the matches. A match tuple describes a span + `doc[start:end]`. The `label_id` and `key` are both integers. """ - if acceptor is not None: - raise ValueError( - "acceptor keyword argument to Matcher deprecated. Specify acceptor " - "functions when you add patterns instead.") cdef vector[StateC] partials cdef int n_partials = 0 cdef int q = 0 @@ -388,13 +336,7 @@ cdef class Matcher: end = token_i+1 ent_id = state.second[1].attrs[0].value label = state.second[1].attrs[1].value - acceptor = self._acceptors.get(ent_id) - if acceptor is None: - matches.append((ent_id, label, start, end)) - else: - match = acceptor(doc, ent_id, label, start, end) - if match: - matches.append(match) + matches.append((ent_id, start, end)) partials.resize(q) # Check whether we open any new patterns on this token for pattern in self.patterns: @@ -419,13 +361,7 @@ cdef class Matcher: end = token_i+1 ent_id = pattern[1].attrs[0].value label = pattern[1].attrs[1].value - acceptor = self._acceptors.get(ent_id) - if acceptor is None: - matches.append((ent_id, label, start, end)) - else: - match = acceptor(doc, ent_id, label, start, end) - if match: - matches.append(match) + matches.append((ent_id, start, end)) # Look for open patterns that are actually satisfied for state in partials: while state.second.quantifier in (ZERO, ZERO_PLUS): @@ -435,36 +371,19 @@ cdef class Matcher: end = len(doc) ent_id = state.second.attrs[0].value label = state.second.attrs[0].value - acceptor = self._acceptors.get(ent_id) - if acceptor is None: - matches.append((ent_id, label, start, end)) - else: - match = acceptor(doc, ent_id, label, start, end) - if match: - matches.append(match) - for i, (ent_id, label, start, end) in enumerate(matches): + matches.append((ent_id, start, end)) + for i, (ent_id, start, end) in enumerate(matches): on_match = self._callbacks.get(ent_id) if on_match is not None: on_match(self, doc, i, matches) + # TODO: only return (match_id, start, end) return matches - def pipe(self, docs, batch_size=1000, n_threads=2): - """ - Match a stream of documents, yielding them in turn. - - Arguments: - docs: A stream of documents. - batch_size (int): - The number of documents to accumulate into a working set. - n_threads (int): - The number of threads with which to work on the buffer in parallel, - if the Matcher implementation supports multi-threading. - Yields: - Doc Documents, in order. - """ - for doc in docs: - self(doc) - yield doc + def _normalize_key(self, key): + if isinstance(key, basestring): + return self.vocab.strings.add(key) + else: + return key def get_bilou(length): @@ -550,7 +469,7 @@ cdef class PhraseMatcher: self(doc) yield doc - def accept_match(self, Doc doc, int ent_id, int label, int start, int end): + def accept_match(self, Doc doc, attr_t ent_id, attr_t label, int start, int end): assert (end - start) < self.max_length cdef int i, j for i in range(self.max_length): diff --git a/spacy/morphology.pxd b/spacy/morphology.pxd index 4d981b30d..922843d6d 100644 --- a/spacy/morphology.pxd +++ b/spacy/morphology.pxd @@ -30,6 +30,7 @@ cdef class Morphology: cdef public object n_tags cdef public object reverse_index cdef public object tag_names + cdef public object exc cdef RichTagC* rich_tags cdef PreshMapArray _cache diff --git a/spacy/morphology.pyx b/spacy/morphology.pyx index 02da21f09..13a0ed8e3 100644 --- a/spacy/morphology.pyx +++ b/spacy/morphology.pyx @@ -33,36 +33,43 @@ def _normalize_props(props): cdef class Morphology: - def __init__(self, StringStore string_store, tag_map, lemmatizer): + def __init__(self, StringStore string_store, tag_map, lemmatizer, exc=None): self.mem = Pool() self.strings = string_store self.tag_map = {} self.lemmatizer = lemmatizer - self.n_tags = len(tag_map) + 1 + self.n_tags = len(tag_map) self.tag_names = tuple(sorted(tag_map.keys())) self.reverse_index = {} self.rich_tags = self.mem.alloc(self.n_tags, sizeof(RichTagC)) for i, (tag_str, attrs) in enumerate(sorted(tag_map.items())): - attrs = _normalize_props(attrs) self.tag_map[tag_str] = dict(attrs) + attrs = _normalize_props(attrs) attrs = intify_attrs(attrs, self.strings, _do_deprecated=True) self.rich_tags[i].id = i - self.rich_tags[i].name = self.strings[tag_str] + self.rich_tags[i].name = self.strings.add(tag_str) self.rich_tags[i].morph = 0 self.rich_tags[i].pos = attrs[POS] self.reverse_index[self.rich_tags[i].name] = i self._cache = PreshMapArray(self.n_tags) + self.exc = {} + if exc is not None: + for (tag_str, orth_str), attrs in exc.items(): + self.add_special_case(tag_str, orth_str, attrs) def __reduce__(self): - return (Morphology, (self.strings, self.tag_map, self.lemmatizer), None, None) + return (Morphology, (self.strings, self.tag_map, self.lemmatizer, + self.exc), None, None) cdef int assign_tag(self, TokenC* token, tag) except -1: if isinstance(tag, basestring): - tag_id = self.reverse_index[self.strings[tag]] - else: + tag = self.strings.add(tag) + if tag in self.reverse_index: tag_id = self.reverse_index[tag] - self.assign_tag_id(token, tag_id) + self.assign_tag_id(token, tag_id) + else: + token.tag = tag cdef int assign_tag_id(self, TokenC* token, int tag_id) except -1: if tag_id >= self.n_tags: @@ -73,7 +80,7 @@ cdef class Morphology: # the statistical model fails. # Related to Issue #220 if Lexeme.c_check_flag(token.lex, IS_SPACE): - tag_id = self.reverse_index[self.strings['SP']] + tag_id = self.reverse_index[self.strings.add('SP')] rich_tag = self.rich_tags[tag_id] analysis = self._cache.get(tag_id, token.lex.orth) if analysis is NULL: @@ -104,7 +111,8 @@ cdef class Morphology: tag (unicode): The part-of-speech tag to key the exception. orth (unicode): The word-form to key the exception. """ - tag = self.strings[tag_str] + self.exc[(tag_str, orth_str)] = dict(attrs) + tag = self.strings.add(tag_str) tag_id = self.reverse_index[tag] orth = self.strings[orth_str] cdef RichTagC rich_tag = self.rich_tags[tag_id] @@ -140,14 +148,14 @@ cdef class Morphology: def lemmatize(self, const univ_pos_t univ_pos, attr_t orth, morphology): cdef unicode py_string = self.strings[orth] if self.lemmatizer is None: - return self.strings[py_string.lower()] + return self.strings.add(py_string.lower()) if univ_pos not in (NOUN, VERB, ADJ, PUNCT): - return self.strings[py_string.lower()] + return self.strings.add(py_string.lower()) cdef set lemma_strings cdef unicode lemma_string lemma_strings = self.lemmatizer(py_string, univ_pos, morphology) lemma_string = sorted(lemma_strings)[0] - lemma = self.strings[lemma_string] + lemma = self.strings.add(lemma_string) return lemma diff --git a/spacy/pipeline.pyx b/spacy/pipeline.pyx index b669e95ec..b87f73c27 100644 --- a/spacy/pipeline.pyx +++ b/spacy/pipeline.pyx @@ -9,12 +9,18 @@ import numpy cimport numpy as np import cytoolz import util +from collections import OrderedDict +import ujson +import msgpack -from thinc.api import add, layerize, chain, clone, concatenate +from thinc.api import add, layerize, chain, clone, concatenate, with_flatten from thinc.neural import Model, Maxout, Softmax, Affine from thinc.neural._classes.hash_embed import HashEmbed from thinc.neural.util import to_categorical +from thinc.neural.pooling import Pooling, max_pool, mean_pool +from thinc.neural._classes.difference import Siamese, CauchySimilarity + from thinc.neural._classes.convolution import ExtractWindow from thinc.neural._classes.resnet import Residual from thinc.neural._classes.batchnorm import BatchNorm as BN @@ -31,110 +37,243 @@ from .syntax.stateclass cimport StateClass from .gold cimport GoldParse from .morphology cimport Morphology from .vocab cimport Vocab +from .syntax import nonproj +from .compat import json_dumps from .attrs import ID, LOWER, PREFIX, SUFFIX, SHAPE, TAG, DEP, POS -from ._ml import Tok2Vec, flatten, get_col, doc2feats +from ._ml import rebatch, Tok2Vec, flatten, get_col, doc2feats +from ._ml import build_text_classifier, build_tagger_model from .parts_of_speech import X -class TokenVectorEncoder(object): - '''Assign position-sensitive vectors to tokens, using a CNN or RNN.''' - name = 'tok2vec' +class BaseThincComponent(object): + name = None @classmethod - def Model(cls, width=128, embed_size=5000, **cfg): - width = util.env_opt('token_vector_width', width) - embed_size = util.env_opt('embed_size', embed_size) - return Tok2Vec(width, embed_size, preprocess=None) + def Model(cls, *shape, **kwargs): + raise NotImplementedError def __init__(self, vocab, model=True, **cfg): - self.vocab = vocab - self.doc2feats = doc2feats() - self.model = model + raise NotImplementedError - def __call__(self, docs, state=None): - if isinstance(docs, Doc): - docs = [docs] - tokvecs = self.predict(docs) - self.set_annotations(docs, tokvecs) - state = {} if state is None else state - state['tokvecs'] = tokvecs - return state + def __call__(self, doc): + scores = self.predict([doc]) + self.set_annotations([doc], scores) + return doc def pipe(self, stream, batch_size=128, n_threads=-1): - for batch in cytoolz.partition_all(batch_size, stream): - docs, states = zip(*batch) - tokvecs = self.predict(docs) - self.set_annotations(docs, tokvecs) - for state in states: - state['tokvecs'] = tokvecs - yield from zip(docs, states) + for docs in cytoolz.partition_all(batch_size, stream): + docs = list(docs) + scores = self.predict(docs) + self.set_annotations(docs, scores) + yield from docs def predict(self, docs): - feats = self.doc2feats(docs) - tokvecs = self.model(feats) - return tokvecs + raise NotImplementedError - def set_annotations(self, docs, tokvecs): - start = 0 - for doc in docs: - doc.tensor = tokvecs[start : start + len(doc)] - start += len(doc) + def set_annotations(self, docs, scores): + raise NotImplementedError - def update(self, docs, golds, state=None, - drop=0., sgd=None): - if isinstance(docs, Doc): - docs = [docs] - golds = [golds] - state = {} if state is None else state - feats = self.doc2feats(docs) - tokvecs, bp_tokvecs = self.model.begin_update(feats, drop=drop) - state['feats'] = feats - state['tokvecs'] = tokvecs - state['bp_tokvecs'] = bp_tokvecs - return state + def update(self, docs_tensors, golds, state=None, drop=0., sgd=None, losses=None): + raise NotImplementedError def get_loss(self, docs, golds, scores): raise NotImplementedError - def begin_training(self, gold_tuples, pipeline=None): - self.doc2feats = doc2feats() + def begin_training(self, gold_tuples=tuple(), pipeline=None): + token_vector_width = pipeline[0].model.nO if self.model is True: - self.model = self.Model() + self.model = self.Model(1, token_vector_width) def use_params(self, params): with self.model.use_params(params): yield + def to_bytes(self, **exclude): + serialize = OrderedDict(( + ('model', lambda: self.model.to_bytes()), + ('vocab', lambda: self.vocab.to_bytes()) + )) + return util.to_bytes(serialize, exclude) -class NeuralTagger(object): - name = 'nn_tagger' - def __init__(self, vocab, model=True): + def from_bytes(self, bytes_data, **exclude): + if self.model is True: + self.model = self.Model() + deserialize = OrderedDict(( + ('model', lambda b: self.model.from_bytes(b)), + ('vocab', lambda b: self.vocab.from_bytes(b)) + )) + util.from_bytes(bytes_data, deserialize, exclude) + return self + + def to_disk(self, path, **exclude): + serialize = OrderedDict(( + ('model', lambda p: p.open('wb').write(self.model.to_bytes())), + ('vocab', lambda p: self.vocab.to_disk(p)), + ('cfg', lambda p: p.open('w').write(json_dumps(self.cfg))) + )) + util.to_disk(path, serialize, exclude) + + def from_disk(self, path, **exclude): + if self.model is True: + self.model = self.Model() + deserialize = OrderedDict(( + ('model', lambda p: self.model.from_bytes(p.open('rb').read())), + ('vocab', lambda p: self.vocab.from_disk(p)), + ('cfg', lambda p: self.cfg.update(_load_cfg(p))) + )) + util.from_disk(path, deserialize, exclude) + return self + + +def _load_cfg(path): + if path.exists(): + return ujson.load(path.open()) + else: + return {} + + +class TokenVectorEncoder(BaseThincComponent): + """Assign position-sensitive vectors to tokens, using a CNN or RNN.""" + name = 'tensorizer' + + @classmethod + def Model(cls, width=128, embed_size=7500, **cfg): + """Create a new statistical model for the class. + + width (int): Output size of the model. + embed_size (int): Number of vectors in the embedding table. + **cfg: Config parameters. + RETURNS (Model): A `thinc.neural.Model` or similar instance. + """ + width = util.env_opt('token_vector_width', width) + embed_size = util.env_opt('embed_size', embed_size) + return Tok2Vec(width, embed_size, preprocess=None) + + def __init__(self, vocab, model=True, **cfg): + """Construct a new statistical model. Weights are not allocated on + initialisation. + + vocab (Vocab): A `Vocab` instance. The model must share the same `Vocab` + instance with the `Doc` objects it will process. + model (Model): A `Model` instance or `True` allocate one later. + **cfg: Config parameters. + + EXAMPLE: + >>> from spacy.pipeline import TokenVectorEncoder + >>> tok2vec = TokenVectorEncoder(nlp.vocab) + >>> tok2vec.model = tok2vec.Model(128, 5000) + """ self.vocab = vocab + self.doc2feats = doc2feats() self.model = model + self.cfg = dict(cfg) - def __call__(self, doc, state=None): - assert state is not None - assert 'tokvecs' in state - tokvecs = state['tokvecs'] - tags = self.predict(tokvecs) - self.set_annotations([doc], tags) - return state + def __call__(self, doc): + """Add context-sensitive vectors to a `Doc`, e.g. from a CNN or LSTM + model. Vectors are set to the `Doc.tensor` attribute. + + docs (Doc or iterable): One or more documents to add vectors to. + RETURNS (dict or None): Intermediate computations. + """ + tokvecses = self.predict([doc]) + self.set_annotations([doc], tokvecses) + return doc def pipe(self, stream, batch_size=128, n_threads=-1): - for batch in cytoolz.partition_all(batch_size, stream): - docs, states = zip(*batch) - tag_ids = self.predict(states[0]['tokvecs']) - self.set_annotations(docs, tag_ids) - for state in states: - state['tag_ids'] = tag_ids - yield from zip(docs, states) + """Process `Doc` objects as a stream. - def predict(self, tokvecs): - scores = self.model(tokvecs) + stream (iterator): A sequence of `Doc` objects to process. + batch_size (int): Number of `Doc` objects to group. + n_threads (int): Number of threads. + YIELDS (iterator): A sequence of `Doc` objects, in order of input. + """ + for docs in cytoolz.partition_all(batch_size, stream): + docs = list(docs) + tokvecses = self.predict(docs) + self.set_annotations(docs, tokvecses) + yield from docs + + def predict(self, docs): + """Return a single tensor for a batch of documents. + + docs (iterable): A sequence of `Doc` objects. + RETURNS (object): Vector representations for each token in the documents. + """ + feats = self.doc2feats(docs) + tokvecs = self.model(feats) + return tokvecs + + def set_annotations(self, docs, tokvecses): + """Set the tensor attribute for a batch of documents. + + docs (iterable): A sequence of `Doc` objects. + tokvecs (object): Vector representation for each token in the documents. + """ + for doc, tokvecs in zip(docs, tokvecses): + assert tokvecs.shape[0] == len(doc) + doc.tensor = tokvecs + + def update(self, docs, golds, state=None, drop=0., sgd=None, losses=None): + """Update the model. + + docs (iterable): A batch of `Doc` objects. + golds (iterable): A batch of `GoldParse` objects. + drop (float): The droput rate. + sgd (callable): An optimizer. + RETURNS (dict): Results from the update. + """ + if isinstance(docs, Doc): + docs = [docs] + feats = self.doc2feats(docs) + tokvecs, bp_tokvecs = self.model.begin_update(feats, drop=drop) + return tokvecs, bp_tokvecs + + def get_loss(self, docs, golds, scores): + # TODO: implement + raise NotImplementedError + + def begin_training(self, gold_tuples=tuple(), pipeline=None): + """Allocate models, pre-process training data and acquire a trainer and + optimizer. + + gold_tuples (iterable): Gold-standard training data. + pipeline (list): The pipeline the model is part of. + """ + self.doc2feats = doc2feats() + if self.model is True: + self.model = self.Model() + + +class NeuralTagger(BaseThincComponent): + name = 'tagger' + def __init__(self, vocab, model=True, **cfg): + self.vocab = vocab + self.model = model + self.cfg = dict(cfg) + + def __call__(self, doc): + tags = self.predict(([doc], [doc.tensor])) + self.set_annotations([doc], tags) + return doc + + def pipe(self, stream, batch_size=128, n_threads=-1): + for docs in cytoolz.partition_all(batch_size, stream): + docs = list(docs) + tokvecs = [d.tensor for d in docs] + tag_ids = self.predict((docs, tokvecs)) + self.set_annotations(docs, tag_ids) + yield from docs + + def predict(self, docs_tokvecs): + scores = self.model(docs_tokvecs) + scores = self.model.ops.flatten(scores) guesses = scores.argmax(axis=1) if not isinstance(guesses, numpy.ndarray): guesses = guesses.get() + tokvecs = docs_tokvecs[1] + guesses = self.model.ops.unflatten(guesses, + [tv.shape[0] for tv in tokvecs]) return guesses def set_annotations(self, docs, batch_tag_ids): @@ -142,49 +281,49 @@ class NeuralTagger(object): docs = [docs] cdef Doc doc cdef int idx = 0 - cdef int i, j, tag_id cdef Vocab vocab = self.vocab for i, doc in enumerate(docs): - doc_tag_ids = batch_tag_ids[idx:idx+len(doc)] + doc_tag_ids = batch_tag_ids[i] for j, tag_id in enumerate(doc_tag_ids): - vocab.morphology.assign_tag_id(&doc.c[j], tag_id) + # Don't clobber preset POS tags + if doc.c[j].tag == 0 and doc.c[j].pos == 0: + vocab.morphology.assign_tag_id(&doc.c[j], tag_id) idx += 1 + doc.is_tagged = True - def update(self, docs, golds, state=None, drop=0., sgd=None): - state = {} if state is None else state + def update(self, docs_tokvecs, golds, drop=0., sgd=None, losses=None): + docs, tokvecs = docs_tokvecs - tokvecs = state['tokvecs'] - bp_tokvecs = state['bp_tokvecs'] if self.model.nI is None: - self.model.nI = tokvecs.shape[1] - - tag_scores, bp_tag_scores = self.model.begin_update(tokvecs, drop=drop) + self.model.nI = tokvecs[0].shape[1] + tag_scores, bp_tag_scores = self.model.begin_update(docs_tokvecs, drop=drop) loss, d_tag_scores = self.get_loss(docs, golds, tag_scores) d_tokvecs = bp_tag_scores(d_tag_scores, sgd=sgd) - - bp_tokvecs(d_tokvecs, sgd=sgd) - - state['tag_scores'] = tag_scores - state['tag_loss'] = loss - return state + return d_tokvecs def get_loss(self, docs, golds, scores): + scores = self.model.ops.flatten(scores) tag_index = {tag: i for i, tag in enumerate(self.vocab.morphology.tag_names)} cdef int idx = 0 correct = numpy.zeros((scores.shape[0],), dtype='i') + guesses = scores.argmax(axis=1) for gold in golds: for tag in gold.tags: - correct[idx] = tag_index[tag] + if tag is None: + correct[idx] = guesses[idx] + else: + correct[idx] = tag_index[tag] idx += 1 correct = self.model.ops.xp.array(correct, dtype='i') d_scores = scores - to_categorical(correct, nb_classes=scores.shape[1]) + d_scores /= d_scores.shape[0] loss = (d_scores**2).sum() - d_scores = self.model.ops.asarray(d_scores, dtype='f') + d_scores = self.model.ops.unflatten(d_scores, [len(d) for d in docs]) return float(loss), d_scores - def begin_training(self, gold_tuples, pipeline=None): + def begin_training(self, gold_tuples=tuple(), pipeline=None): orig_tag_map = dict(self.vocab.morphology.tag_map) new_tag_map = {} for raw_text, annots_brackets in gold_tuples: @@ -195,22 +334,277 @@ class NeuralTagger(object): new_tag_map[tag] = orig_tag_map[tag] else: new_tag_map[tag] = {POS: X} + if 'SP' not in new_tag_map: + new_tag_map['SP'] = orig_tag_map.get('SP', {POS: X}) cdef Vocab vocab = self.vocab - vocab.morphology = Morphology(vocab.strings, new_tag_map, - vocab.morphology.lemmatizer) - self.model = Softmax(self.vocab.morphology.n_tags) - print("Tagging", self.model.nO, "tags") + if new_tag_map: + vocab.morphology = Morphology(vocab.strings, new_tag_map, + vocab.morphology.lemmatizer, + exc=vocab.morphology.exc) + token_vector_width = pipeline[0].model.nO + if self.model is True: + self.model = self.Model(self.vocab.morphology.n_tags, token_vector_width) + @classmethod + def Model(cls, n_tags, token_vector_width): + return build_tagger_model(n_tags, token_vector_width) + def use_params(self, params): with self.model.use_params(params): yield + def to_bytes(self, **exclude): + serialize = OrderedDict(( + ('model', lambda: self.model.to_bytes()), + ('vocab', lambda: self.vocab.to_bytes()), + ('tag_map', lambda: msgpack.dumps(self.vocab.morphology.tag_map, + use_bin_type=True, + encoding='utf8')) + )) + return util.to_bytes(serialize, exclude) + + def from_bytes(self, bytes_data, **exclude): + def load_model(b): + if self.model is True: + token_vector_width = util.env_opt('token_vector_width', 128) + self.model = self.Model(self.vocab.morphology.n_tags, token_vector_width) + self.model.from_bytes(b) + + def load_tag_map(b): + tag_map = msgpack.loads(b, encoding='utf8') + self.vocab.morphology = Morphology( + self.vocab.strings, tag_map=tag_map, + lemmatizer=self.vocab.morphology.lemmatizer, + exc=self.vocab.morphology.exc) + + deserialize = OrderedDict(( + ('vocab', lambda b: self.vocab.from_bytes(b)), + ('tag_map', load_tag_map), + ('model', lambda b: load_model(b)), + )) + util.from_bytes(bytes_data, deserialize, exclude) + return self + + def to_disk(self, path, **exclude): + serialize = OrderedDict(( + ('vocab', lambda p: self.vocab.to_disk(p)), + ('tag_map', lambda p: p.open('wb').write(msgpack.dumps( + self.vocab.morphology.tag_map, + use_bin_type=True, + encoding='utf8'))), + ('model', lambda p: p.open('wb').write(self.model.to_bytes())), + ('cfg', lambda p: p.open('w').write(json_dumps(self.cfg))) + )) + util.to_disk(path, serialize, exclude) + + def from_disk(self, path, **exclude): + def load_model(p): + if self.model is True: + token_vector_width = util.env_opt('token_vector_width', 128) + self.model = self.Model(self.vocab.morphology.n_tags, token_vector_width) + self.model.from_bytes(p.open('rb').read()) + + def load_tag_map(p): + with p.open('rb') as file_: + tag_map = msgpack.loads(file_.read(), encoding='utf8') + self.vocab.morphology = Morphology( + self.vocab.strings, tag_map=tag_map, + lemmatizer=self.vocab.morphology.lemmatizer, + exc=self.vocab.morphology.exc) + + deserialize = OrderedDict(( + ('vocab', lambda p: self.vocab.from_disk(p)), + ('tag_map', load_tag_map), + ('model', load_model), + ('cfg', lambda p: self.cfg.update(_load_cfg(p))) + )) + util.from_disk(path, deserialize, exclude) + return self + + +class NeuralLabeller(NeuralTagger): + name = 'nn_labeller' + def __init__(self, vocab, model=True, **cfg): + self.vocab = vocab + self.model = model + self.cfg = dict(cfg) + + @property + def labels(self): + return self.cfg.setdefault('labels', {}) + + @labels.setter + def labels(self, value): + self.cfg['labels'] = value + + def set_annotations(self, docs, dep_ids): + pass + + def begin_training(self, gold_tuples=tuple(), pipeline=None): + gold_tuples = nonproj.preprocess_training_data(gold_tuples) + for raw_text, annots_brackets in gold_tuples: + for annots, brackets in annots_brackets: + ids, words, tags, heads, deps, ents = annots + for dep in deps: + if dep not in self.labels: + self.labels[dep] = len(self.labels) + token_vector_width = pipeline[0].model.nO + if self.model is True: + self.model = self.Model(len(self.labels), token_vector_width) + + @classmethod + def Model(cls, n_tags, token_vector_width): + return build_tagger_model(n_tags, token_vector_width) + + def get_loss(self, docs, golds, scores): + scores = self.model.ops.flatten(scores) + cdef int idx = 0 + correct = numpy.zeros((scores.shape[0],), dtype='i') + guesses = scores.argmax(axis=1) + for gold in golds: + for tag in gold.labels: + if tag is None or tag not in self.labels: + correct[idx] = guesses[idx] + else: + correct[idx] = self.labels[tag] + idx += 1 + correct = self.model.ops.xp.array(correct, dtype='i') + d_scores = scores - to_categorical(correct, nb_classes=scores.shape[1]) + d_scores /= d_scores.shape[0] + loss = (d_scores**2).sum() + d_scores = self.model.ops.unflatten(d_scores, [len(d) for d in docs]) + return float(loss), d_scores + + +class SimilarityHook(BaseThincComponent): + """ + Experimental + + A pipeline component to install a hook for supervised similarity into + Doc objects. Requires a Tensorizer to pre-process documents. The similarity + model can be any object obeying the Thinc Model interface. By default, + the model concatenates the elementwise mean and elementwise max of the two + tensors, and compares them using the Cauchy-like similarity function + from Chen (2013): + + similarity = 1. / (1. + (W * (vec1-vec2)**2).sum()) + + Where W is a vector of dimension weights, initialized to 1. + """ + name = 'similarity' + def __init__(self, vocab, model=True, **cfg): + self.vocab = vocab + self.model = model + self.cfg = dict(cfg) + + @classmethod + def Model(cls, length): + return Siamese(Pooling(max_pool, mean_pool), CauchySimilarity(length)) + + def __call__(self, doc): + '''Install similarity hook''' + doc.user_hooks['similarity'] = self.predict + return doc + + def pipe(self, docs, **kwargs): + for doc in docs: + yield self(doc) + + def predict(self, doc1, doc2): + return self.model.predict([(doc1.tensor, doc2.tensor)]) + + def update(self, doc1_tensor1_doc2_tensor2, golds, sgd=None, drop=0.): + doc1s, tensor1s, doc2s, tensor2s = doc1_tensor1_doc2_tensor2 + sims, bp_sims = self.model.begin_update(zip(tensor1s, tensor2s), + drop=drop) + d_tensor1s, d_tensor2s = bp_sims(golds, sgd=sgd) + + return d_tensor1s, d_tensor2s + + def begin_training(self, _=tuple(), pipeline=None): + """ + Allocate model, using width from tensorizer in pipeline. + + gold_tuples (iterable): Gold-standard training data. + pipeline (list): The pipeline the model is part of. + """ + if self.model is True: + self.model = self.Model(pipeline[0].model.nO) + + +class TextCategorizer(BaseThincComponent): + name = 'textcat' + + @classmethod + def Model(cls, nr_class=1, width=64, **cfg): + return build_text_classifier(nr_class, width, **cfg) + + def __init__(self, vocab, model=True, **cfg): + self.vocab = vocab + self.model = model + self.cfg = dict(cfg) + + @property + def labels(self): + return self.cfg.get('labels', ['LABEL']) + + @labels.setter + def labels(self, value): + self.cfg['labels'] = value + + def __call__(self, doc): + scores = self.predict([doc]) + self.set_annotations([doc], scores) + return doc + + def pipe(self, stream, batch_size=128, n_threads=-1): + for docs in cytoolz.partition_all(batch_size, stream): + docs = list(docs) + scores = self.predict(docs) + self.set_annotations(docs, scores) + yield from docs + + def predict(self, docs): + scores = self.model(docs) + scores = self.model.ops.asarray(scores) + return scores + + def set_annotations(self, docs, scores): + for i, doc in enumerate(docs): + for j, label in enumerate(self.labels): + doc.cats[label] = float(scores[i, j]) + + def update(self, docs_tensors, golds, state=None, drop=0., sgd=None, losses=None): + docs, tensors = docs_tensors + scores, bp_scores = self.model.begin_update(docs, drop=drop) + loss, d_scores = self.get_loss(docs, golds, scores) + d_tensors = bp_scores(d_scores, sgd=sgd) + if losses is not None: + losses.setdefault(self.name, 0.0) + losses[self.name] += loss + return d_tensors + + def get_loss(self, docs, golds, scores): + truths = numpy.zeros((len(golds), len(self.labels)), dtype='f') + for i, gold in enumerate(golds): + for j, label in enumerate(self.labels): + truths[i, j] = label in gold.cats + truths = self.model.ops.asarray(truths) + d_scores = (scores-truths) / scores.shape[0] + mean_square_error = ((scores-truths)**2).sum(axis=1).mean() + return mean_square_error, d_scores + + def begin_training(self, gold_tuples=tuple(), pipeline=None): + if pipeline: + token_vector_width = pipeline[0].model.nO + else: + token_vector_width = 64 + if self.model is True: + self.model = self.Model(len(self.labels), token_vector_width) cdef class EntityRecognizer(LinearParser): - """ - Annotate named entities on Doc objects. - """ + """Annotate named entities on Doc objects.""" TransitionSystem = BiluoPushDown feature_templates = get_feature_templates('ner') @@ -222,9 +616,7 @@ cdef class EntityRecognizer(LinearParser): cdef class BeamEntityRecognizer(BeamParser): - """ - Annotate named entities on Doc objects. - """ + """Annotate named entities on Doc objects.""" TransitionSystem = BiluoPushDown feature_templates = get_feature_templates('ner') @@ -249,32 +641,26 @@ cdef class NeuralDependencyParser(NeuralParser): name = 'parser' TransitionSystem = ArcEager + def __reduce__(self): + return (NeuralDependencyParser, (self.vocab, self.moves, self.model), None, None) + cdef class NeuralEntityRecognizer(NeuralParser): - name = 'entity' + name = 'ner' TransitionSystem = BiluoPushDown nr_feature = 6 - def get_token_ids(self, states): - cdef StateClass state - cdef int n_tokens = 6 - ids = numpy.zeros((len(states), n_tokens), dtype='i', order='c') - for i, state in enumerate(states): - ids[i, 0] = state.c.B(0)-1 - ids[i, 1] = state.c.B(0) - ids[i, 2] = state.c.B(1) - ids[i, 3] = state.c.E(0) - ids[i, 4] = state.c.E(0)-1 - ids[i, 5] = state.c.E(0)+1 - for j in range(6): - if ids[i, j] >= state.c.length: - ids[i, j] = -1 - if ids[i, j] != -1: - ids[i, j] += state.c.offset - return ids - + def predict_confidences(self, docs): + tensors = [d.tensor for d in docs] + samples = [] + for i in range(10): + states = self.parse_batch(docs, tensors, drop=0.3) + for state in states: + samples.append(self._get_entities(state)) + def __reduce__(self): + return (NeuralEntityRecognizer, (self.vocab, self.moves, self.model), None, None) cdef class BeamDependencyParser(BeamParser): diff --git a/spacy/strings.pxd b/spacy/strings.pxd index d5e320642..0ad403cf1 100644 --- a/spacy/strings.pxd +++ b/spacy/strings.pxd @@ -1,4 +1,5 @@ from libc.stdint cimport int64_t +from libcpp.vector cimport vector from cymem.cymem cimport Pool from preshed.maps cimport PreshMap @@ -8,6 +9,9 @@ from .typedefs cimport attr_t, hash_t cpdef hash_t hash_string(unicode string) except 0 +cdef hash_t hash_utf8(char* utf8_string, int length) nogil + +cdef unicode decode_Utf8Str(const Utf8Str* string) ctypedef union Utf8Str: @@ -17,13 +21,11 @@ ctypedef union Utf8Str: cdef class StringStore: cdef Pool mem - cdef Utf8Str* c - cdef int64_t size cdef bint is_frozen + cdef vector[hash_t] keys cdef public PreshMap _map cdef public PreshMap _oov - cdef int64_t _resize_at cdef const Utf8Str* intern_unicode(self, unicode py_string) cdef const Utf8Str* _intern_utf8(self, char* utf8_string, int length) diff --git a/spacy/strings.pyx b/spacy/strings.pyx index 38afd7f02..2e42b9667 100644 --- a/spacy/strings.pyx +++ b/spacy/strings.pyx @@ -7,11 +7,16 @@ from libc.string cimport memcpy from libc.stdint cimport uint64_t, uint32_t from murmurhash.mrmr cimport hash64, hash32 from preshed.maps cimport map_iter, key_t +from libc.stdint cimport uint32_t +import ujson +import dill + +from .symbols import IDS as SYMBOLS_BY_STR +from .symbols import NAMES as SYMBOLS_BY_INT from .typedefs cimport hash_t -from libc.stdint cimport uint32_t - -import ujson +from . import util +from .compat import json_dumps cpdef hash_t hash_string(unicode string) except 0: @@ -27,7 +32,7 @@ cdef uint32_t hash32_utf8(char* utf8_string, int length) nogil: return hash32(utf8_string, length, 1) -cdef unicode _decode(const Utf8Str* string): +cdef unicode decode_Utf8Str(const Utf8Str* string): cdef int i, length if string.s[0] < sizeof(string.s) and string.s[0] != 0: return string.s[1:string.s[0]+1].decode('utf8') @@ -44,10 +49,10 @@ cdef unicode _decode(const Utf8Str* string): return string.p[i:length + i].decode('utf8') -cdef Utf8Str _allocate(Pool mem, const unsigned char* chars, uint32_t length) except *: +cdef Utf8Str* _allocate(Pool mem, const unsigned char* chars, uint32_t length) except *: cdef int n_length_bytes cdef int i - cdef Utf8Str string + cdef Utf8Str* string = mem.alloc(1, sizeof(Utf8Str)) cdef uint32_t ulength = length if length < sizeof(string.s): string.s[0] = length @@ -72,129 +77,166 @@ cdef Utf8Str _allocate(Pool mem, const unsigned char* chars, uint32_t length) ex cdef class StringStore: - """ - Map strings to and from integer IDs. - """ + """Look up strings by 64-bit hashes.""" def __init__(self, strings=None, freeze=False): - """ - Create the StringStore. + """Create the StringStore. - Arguments: - strings: A sequence of unicode strings to add to the store. + strings (iterable): A sequence of unicode strings to add to the store. + RETURNS (StringStore): The newly constructed object. """ self.mem = Pool() self._map = PreshMap() self._oov = PreshMap() - self._resize_at = 10000 - self.c = self.mem.alloc(self._resize_at, sizeof(Utf8Str)) - self.size = 1 self.is_frozen = freeze if strings is not None: for string in strings: - _ = self[string] - - property size: - def __get__(self): - return self.size -1 - - def __reduce__(self): - # TODO: OOV words, for the is_frozen stuff? - if self.is_frozen: - raise NotImplementedError( - "Currently missing support for pickling StringStore when " - "is_frozen=True") - return (StringStore, (list(self),)) - - def __len__(self): - """ - The number of strings in the store. - - Returns: - int The number of strings in the store. - """ - return self.size-1 + self.add(string) def __getitem__(self, object string_or_id): - """ - Retrieve a string from a given integer ID, or vice versa. + """Retrieve a string from a given hash, or vice versa. - Arguments: - string_or_id (bytes or unicode or int): - The value to encode. - Returns: - unicode or int: The value to retrieved. + string_or_id (bytes, unicode or uint64): The value to encode. + Returns (unicode or uint64): The value to be retrieved. """ if isinstance(string_or_id, basestring) and len(string_or_id) == 0: return 0 elif string_or_id == 0: return u'' + elif string_or_id in SYMBOLS_BY_STR: + return SYMBOLS_BY_STR[string_or_id] - cdef bytes byte_string - cdef const Utf8Str* utf8str - cdef uint64_t int_id - cdef uint32_t oov_id - if isinstance(string_or_id, (int, long)): - int_id = string_or_id - oov_id = string_or_id - if int_id < self.size: - return _decode(&self.c[int_id]) - else: - utf8str = self._oov.get(oov_id) - if utf8str is not NULL: - return _decode(utf8str) - else: - raise IndexError(string_or_id) + cdef hash_t key + + if isinstance(string_or_id, unicode): + key = hash_string(string_or_id) + return key + elif isinstance(string_or_id, bytes): + key = hash_utf8(string_or_id, len(string_or_id)) + return key + elif string_or_id < len(SYMBOLS_BY_INT): + return SYMBOLS_BY_INT[string_or_id] else: - if isinstance(string_or_id, bytes): - byte_string = string_or_id - elif isinstance(string_or_id, unicode): - byte_string = (string_or_id).encode('utf8') - else: - raise TypeError(type(string_or_id)) - utf8str = self._intern_utf8(byte_string, len(byte_string)) + key = string_or_id + utf8str = self._map.get(key) if utf8str is NULL: - # TODO: We need to use 32 bit here, for compatibility with the - # vocabulary values. This makes birthday paradox probabilities - # pretty bad. - # We could also get unlucky here, and hash into a value that - # collides with the 'real' strings. - return hash32_utf8(byte_string, len(byte_string)) + raise KeyError(string_or_id) else: - return utf8str - self.c + return decode_Utf8Str(utf8str) - def __contains__(self, unicode string not None): - """ - Check whether a string is in the store. + def add(self, string): + """Add a string to the StringStore. - Arguments: - string (unicode): The string to check. - Returns bool: - Whether the store contains the string. + string (unicode): The string to add. + RETURNS (uint64): The string's hash value. """ - if len(string) == 0: + if isinstance(string, unicode): + if string in SYMBOLS_BY_STR: + return SYMBOLS_BY_STR[string] + key = hash_string(string) + self.intern_unicode(string) + elif isinstance(string, bytes): + if string in SYMBOLS_BY_STR: + return SYMBOLS_BY_STR[string] + key = hash_utf8(string, len(string)) + self._intern_utf8(string, len(string)) + else: + raise TypeError( + "Can only add unicode or bytes. Got type: %s" % type(string)) + return key + + def __len__(self): + """The number of strings in the store. + + RETURNS (int): The number of strings in the store. + """ + return self.keys.size() + + def __contains__(self, string not None): + """Check whether a string is in the store. + + string (unicode): The string to check. + RETURNS (bool): Whether the store contains the string. + """ + cdef hash_t key + if isinstance(string, int) or isinstance(string, long): + if string == 0: + return True + key = string + elif len(string) == 0: return True - cdef hash_t key = hash_string(string) - return self._map.get(key) is not NULL + elif string in SYMBOLS_BY_STR: + return True + elif isinstance(string, unicode): + key = hash_string(string) + else: + string = string.encode('utf8') + key = hash_utf8(string, len(string)) + if key < len(SYMBOLS_BY_INT): + return True + else: + return self._map.get(key) is not NULL def __iter__(self): - """ - Iterate over the strings in the store, in order. + """Iterate over the strings in the store, in order. - Yields: unicode A string in the store. + YIELDS (unicode): A string in the store. """ cdef int i - for i in range(self.size): - yield _decode(&self.c[i]) if i > 0 else u'' + cdef hash_t key + for i in range(self.keys.size()): + key = self.keys[i] + utf8str = self._map.get(key) + yield decode_Utf8Str(utf8str) # TODO: Iterate OOV here? def __reduce__(self): - strings = [""] - for i in range(1, self.size): - string = &self.c[i] - py_string = _decode(string) - strings.append(py_string) + strings = list(self) return (StringStore, (strings,), None, None, None) + def to_disk(self, path): + """Save the current state to a directory. + + path (unicode or Path): A path to a directory, which will be created if + it doesn't exist. Paths may be either strings or `Path`-like objects. + """ + path = util.ensure_path(path) + strings = list(self) + with path.open('w') as file_: + file_.write(json_dumps(strings)) + + def from_disk(self, path): + """Loads state from a directory. Modifies the object in place and + returns it. + + path (unicode or Path): A path to a directory. Paths may be either + strings or `Path`-like objects. + RETURNS (StringStore): The modified `StringStore` object. + """ + path = util.ensure_path(path) + with path.open('r') as file_: + strings = ujson.load(file_) + self._reset_and_load(strings) + return self + + def to_bytes(self, **exclude): + """Serialize the current state to a binary string. + + **exclude: Named attributes to prevent from being serialized. + RETURNS (bytes): The serialized form of the `StringStore` object. + """ + return ujson.dumps(list(self)) + + def from_bytes(self, bytes_data, **exclude): + """Load state from a binary string. + + bytes_data (bytes): The data to load from. + **exclude: Named attributes to prevent from being loaded. + RETURNS (StringStore): The `StringStore` object. + """ + strings = ujson.loads(bytes_data) + self._reset_and_load(strings) + return self + def set_frozen(self, bint is_frozen): # TODO self.is_frozen = is_frozen @@ -202,6 +244,15 @@ cdef class StringStore: def flush_oov(self): self._oov = PreshMap() + def _reset_and_load(self, strings, freeze=False): + self.mem = Pool() + self._map = PreshMap() + self._oov = PreshMap() + self.keys.clear() + for string in strings: + self.add(string) + self.is_frozen = freeze + cdef const Utf8Str* intern_unicode(self, unicode py_string): # 0 means missing, but we don't bother offsetting the index. cdef bytes byte_string = py_string.encode('utf8') @@ -223,73 +274,11 @@ cdef class StringStore: key32 = hash32_utf8(utf8_string, length) # Important: Make the OOV store own the memory. That way it's trivial # to flush them all. - value = self._oov.mem.alloc(1, sizeof(Utf8Str)) - value[0] = _allocate(self._oov.mem, utf8_string, length) + value = _allocate(self._oov.mem, utf8_string, length) self._oov.set(key32, value) return NULL - if self.size == self._resize_at: - self._realloc() - self.c[self.size] = _allocate(self.mem, utf8_string, length) - self._map.set(key, &self.c[self.size]) - self.size += 1 - return &self.c[self.size-1] - - def dump(self, file_): - """ - Save the strings to a JSON file. - - Arguments: - file_ (buffer): The file to save the strings. - Returns: - None - """ - string_data = ujson.dumps(list(self)) - if not isinstance(string_data, unicode): - string_data = string_data.decode('utf8') - # TODO: OOV? - file_.write(string_data) - - def load(self, file_): - """ - Load the strings from a JSON file. - - Arguments: - file_ (buffer): The file from which to load the strings. - Returns: - None - """ - strings = ujson.load(file_) - if strings == ['']: - return None - cdef unicode string - for string in strings: - # explicit None/len check instead of simple truth testing - # (bug in Cython <= 0.23.4) - if string is not None and len(string): - self.intern_unicode(string) - - def _realloc(self): - # We want to map straight to pointers, but they'll be invalidated if - # we resize our array. So, first we remap to indices, then we resize, - # then we can acquire the new pointers. - cdef Pool tmp_mem = Pool() - keys = tmp_mem.alloc(self.size, sizeof(key_t)) - cdef key_t key - cdef void* value - cdef const Utf8Str ptr - cdef int i = 0 - cdef size_t offset - while map_iter(self._map.c_map, &i, &key, &value): - # Find array index with pointer arithmetic - offset = ((value) - self.c) - keys[offset] = key - - self._resize_at *= 2 - cdef size_t new_size = self._resize_at * sizeof(Utf8Str) - self.c = self.mem.realloc(self.c, new_size) - - self._map = PreshMap(self.size) - for i in range(self.size): - if keys[i]: - self._map.set(keys[i], &self.c[i]) + value = _allocate(self.mem, utf8_string, length) + self._map.set(key, value) + self.keys.push_back(key) + return value diff --git a/spacy/structs.pxd b/spacy/structs.pxd index 41bfbb62c..3c60cd87f 100644 --- a/spacy/structs.pxd +++ b/spacy/structs.pxd @@ -5,8 +5,6 @@ from .parts_of_speech cimport univ_pos_t cdef struct LexemeC: - float* vector - flags_t flags attr_t lang @@ -25,11 +23,10 @@ cdef struct LexemeC: float prob float sentiment - float l2_norm cdef struct SerializedLexemeC: - unsigned char[4*13 + 8] data + unsigned char[8 + 8*10 + 4 + 4] data # sizeof(flags_t) # flags # + sizeof(attr_t) # lang # + sizeof(attr_t) # id @@ -50,7 +47,7 @@ cdef struct Entity: hash_t id int start int end - int label + attr_t label cdef struct TokenC: @@ -58,12 +55,12 @@ cdef struct TokenC: uint64_t morph univ_pos_t pos bint spacy - int tag + attr_t tag int idx - int lemma - int sense + attr_t lemma + attr_t sense int head - int dep + attr_t dep bint sent_start uint32_t l_kids @@ -72,5 +69,5 @@ cdef struct TokenC: uint32_t r_edge int ent_iob - int ent_type # TODO: Is there a better way to do this? Multiple sources of truth.. + attr_t ent_type # TODO: Is there a better way to do this? Multiple sources of truth.. hash_t ent_id diff --git a/spacy/symbols.pxd b/spacy/symbols.pxd index 1a46f509f..0b713cb21 100644 --- a/spacy/symbols.pxd +++ b/spacy/symbols.pxd @@ -82,6 +82,7 @@ cpdef enum symbol_t: ENT_IOB ENT_TYPE HEAD + SENT_START SPACY PROB diff --git a/spacy/symbols.pyx b/spacy/symbols.pyx index 662aca777..9f4009579 100644 --- a/spacy/symbols.pyx +++ b/spacy/symbols.pyx @@ -84,6 +84,7 @@ IDS = { "ENT_IOB": ENT_IOB, "ENT_TYPE": ENT_TYPE, "HEAD": HEAD, + "SENT_START": SENT_START, "SPACY": SPACY, "PROB": PROB, diff --git a/spacy/syntax/_state.pxd b/spacy/syntax/_state.pxd index f27580de5..c06851978 100644 --- a/spacy/syntax/_state.pxd +++ b/spacy/syntax/_state.pxd @@ -9,6 +9,7 @@ from ..structs cimport TokenC, Entity from ..lexeme cimport Lexeme from ..symbols cimport punct from ..attrs cimport IS_SPACE +from ..typedefs cimport attr_t cdef inline bint is_space_token(const TokenC* token) nogil: @@ -71,6 +72,45 @@ cdef cppclass StateC: free(this._stack - PADDING) free(this.shifted - PADDING) + void set_context_tokens(int* ids, int n) nogil: + if n == 13: + ids[0] = this.B(0) + ids[1] = this.B(1) + ids[2] = this.S(0) + ids[3] = this.S(1) + ids[4] = this.S(2) + ids[5] = this.L(this.S(0), 1) + ids[6] = this.L(this.S(0), 2) + ids[6] = this.R(this.S(0), 1) + ids[7] = this.L(this.B(0), 1) + ids[8] = this.R(this.S(0), 2) + ids[9] = this.L(this.S(1), 1) + ids[10] = this.L(this.S(1), 2) + ids[11] = this.R(this.S(1), 1) + ids[12] = this.R(this.S(1), 2) + elif n == 6: + if this.B(0) >= 0: + ids[0] = this.B(0) + else: + ids[0] = -1 + ids[1] = this.B(0) + ids[2] = this.B(1) + ids[3] = this.E(0) + if ids[3] >= 1: + ids[4] = this.E(0)-1 + else: + ids[4] = -1 + if (ids[3]+1) < this.length: + ids[5] = this.E(0)+1 + else: + ids[5] = -1 + else: + # TODO error =/ + pass + for i in range(n): + if ids[i] >= 0: + ids[i] += this.offset + int S(int i) nogil const: if i >= this._s_i: return -1 @@ -238,7 +278,7 @@ cdef cppclass StateC: this._s_i -= 1 this.shifted[this.B(0)] = True - void add_arc(int head, int child, int label) nogil: + void add_arc(int head, int child, attr_t label) nogil: if this.has_head(child): this.del_arc(this.H(child), child) @@ -282,7 +322,7 @@ cdef cppclass StateC: h.l_edge = this.L_(h_i, 2).l_edge if h.l_kids >= 2 else h_i h.l_kids -= 1 - void open_ent(int label) nogil: + void open_ent(attr_t label) nogil: this._ents[this._e_i].start = this.B(0) this._ents[this._e_i].label = label this._ents[this._e_i].end = -1 @@ -294,7 +334,7 @@ cdef cppclass StateC: this._ents[this._e_i-1].end = this.B(0)+1 this._sent[this.B(0)].ent_iob = 1 - void set_ent_tag(int i, int ent_iob, int ent_type) nogil: + void set_ent_tag(int i, int ent_iob, attr_t ent_type) nogil: if 0 <= i < this.length: this._sent[i].ent_iob = ent_iob this._sent[i].ent_type = ent_type @@ -305,16 +345,18 @@ cdef cppclass StateC: this._break = this._b_i void clone(const StateC* src) nogil: + this.length = src.length memcpy(this._sent, src._sent, this.length * sizeof(TokenC)) memcpy(this._stack, src._stack, this.length * sizeof(int)) memcpy(this._buffer, src._buffer, this.length * sizeof(int)) memcpy(this._ents, src._ents, this.length * sizeof(Entity)) memcpy(this.shifted, src.shifted, this.length * sizeof(this.shifted[0])) - this.length = src.length this._b_i = src._b_i this._s_i = src._s_i this._e_i = src._e_i this._break = src._break + this.offset = src.offset + this._empty_token = src._empty_token void fast_forward() nogil: # space token attachement policy: diff --git a/spacy/syntax/arc_eager.pxd b/spacy/syntax/arc_eager.pxd index 99b2da41a..972ad682a 100644 --- a/spacy/syntax/arc_eager.pxd +++ b/spacy/syntax/arc_eager.pxd @@ -3,6 +3,7 @@ from cymem.cymem cimport Pool from thinc.typedefs cimport weight_t from .stateclass cimport StateClass +from ..typedefs cimport attr_t from .transition_system cimport TransitionSystem, Transition from ..gold cimport GoldParseC diff --git a/spacy/syntax/arc_eager.pyx b/spacy/syntax/arc_eager.pyx index 974f62558..9477449a5 100644 --- a/spacy/syntax/arc_eager.pyx +++ b/spacy/syntax/arc_eager.pyx @@ -9,10 +9,12 @@ import ctypes from libc.stdint cimport uint32_t from libc.string cimport memcpy from cymem.cymem cimport Pool +from collections import OrderedDict +from thinc.extra.search cimport Beam +import numpy from .stateclass cimport StateClass from ._state cimport StateC, is_space_token -from .nonproj import PseudoProjectivity from .nonproj import is_nonproj_tree from .transition_system cimport do_func_t, get_cost_func_t from .transition_system cimport move_cost_func_t, label_cost_func_t @@ -60,7 +62,7 @@ cdef weight_t push_cost(StateClass stcls, const GoldParseC* gold, int target) no cost += 1 if gold.heads[S_i] == target and (NON_MONOTONIC or not stcls.has_head(S_i)): cost += 1 - cost += Break.is_valid(stcls.c, -1) and Break.move_cost(stcls, gold) == 0 + cost += Break.is_valid(stcls.c, 0) and Break.move_cost(stcls, gold) == 0 return cost @@ -73,7 +75,7 @@ cdef weight_t pop_cost(StateClass stcls, const GoldParseC* gold, int target) nog cost += gold.heads[target] == B_i if gold.heads[B_i] == B_i or gold.heads[B_i] < target: break - if Break.is_valid(stcls.c, -1) and Break.move_cost(stcls, gold) == 0: + if Break.is_valid(stcls.c, 0) and Break.move_cost(stcls, gold) == 0: cost += 1 return cost @@ -84,14 +86,14 @@ cdef weight_t arc_cost(StateClass stcls, const GoldParseC* gold, int head, int c elif stcls.H(child) == gold.heads[child]: return 1 # Head in buffer - elif gold.heads[child] >= stcls.B(0) and stcls.B(1) != -1: + elif gold.heads[child] >= stcls.B(0) and stcls.B(1) != 0: return 1 else: return 0 cdef bint arc_is_gold(const GoldParseC* gold, int head, int child) nogil: - if gold.labels[child] == -1: + if not gold.has_dep[child]: return True elif gold.heads[child] == head: return True @@ -99,10 +101,10 @@ cdef bint arc_is_gold(const GoldParseC* gold, int head, int child) nogil: return False -cdef bint label_is_gold(const GoldParseC* gold, int head, int child, int label) nogil: - if gold.labels[child] == -1: +cdef bint label_is_gold(const GoldParseC* gold, int head, int child, attr_t label) nogil: + if not gold.has_dep[child]: return True - elif label == -1: + elif label == 0: return True elif gold.labels[child] == label: return True @@ -111,21 +113,20 @@ cdef bint label_is_gold(const GoldParseC* gold, int head, int child, int label) cdef bint _is_gold_root(const GoldParseC* gold, int word) nogil: - return gold.labels[word] == -1 or gold.heads[word] == word - + return gold.heads[word] == word or not gold.has_dep[word] cdef class Shift: @staticmethod - cdef bint is_valid(const StateC* st, int label) nogil: + cdef bint is_valid(const StateC* st, attr_t label) nogil: return st.buffer_length() >= 2 and not st.shifted[st.B(0)] and not st.B_(0).sent_start @staticmethod - cdef int transition(StateC* st, int label) nogil: + cdef int transition(StateC* st, attr_t label) nogil: st.push() st.fast_forward() @staticmethod - cdef weight_t cost(StateClass st, const GoldParseC* gold, int label) nogil: + cdef weight_t cost(StateClass st, const GoldParseC* gold, attr_t label) nogil: return Shift.move_cost(st, gold) + Shift.label_cost(st, gold, label) @staticmethod @@ -133,17 +134,17 @@ cdef class Shift: return push_cost(s, gold, s.B(0)) @staticmethod - cdef inline weight_t label_cost(StateClass s, const GoldParseC* gold, int label) nogil: + cdef inline weight_t label_cost(StateClass s, const GoldParseC* gold, attr_t label) nogil: return 0 cdef class Reduce: @staticmethod - cdef bint is_valid(const StateC* st, int label) nogil: + cdef bint is_valid(const StateC* st, attr_t label) nogil: return st.stack_depth() >= 2 @staticmethod - cdef int transition(StateC* st, int label) nogil: + cdef int transition(StateC* st, attr_t label) nogil: if st.has_head(st.S(0)): st.pop() else: @@ -151,7 +152,7 @@ cdef class Reduce: st.fast_forward() @staticmethod - cdef weight_t cost(StateClass s, const GoldParseC* gold, int label) nogil: + cdef weight_t cost(StateClass s, const GoldParseC* gold, attr_t label) nogil: return Reduce.move_cost(s, gold) + Reduce.label_cost(s, gold, label) @staticmethod @@ -165,28 +166,28 @@ cdef class Reduce: cost -= 1 if gold.heads[S_i] == st.S(0): cost -= 1 - if Break.is_valid(st.c, -1) and Break.move_cost(st, gold) == 0: + if Break.is_valid(st.c, 0) and Break.move_cost(st, gold) == 0: cost -= 1 return cost @staticmethod - cdef inline weight_t label_cost(StateClass s, const GoldParseC* gold, int label) nogil: + cdef inline weight_t label_cost(StateClass s, const GoldParseC* gold, attr_t label) nogil: return 0 cdef class LeftArc: @staticmethod - cdef bint is_valid(const StateC* st, int label) nogil: + cdef bint is_valid(const StateC* st, attr_t label) nogil: return not st.B_(0).sent_start @staticmethod - cdef int transition(StateC* st, int label) nogil: + cdef int transition(StateC* st, attr_t label) nogil: st.add_arc(st.B(0), st.S(0), label) st.pop() st.fast_forward() @staticmethod - cdef weight_t cost(StateClass s, const GoldParseC* gold, int label) nogil: + cdef weight_t cost(StateClass s, const GoldParseC* gold, attr_t label) nogil: return LeftArc.move_cost(s, gold) + LeftArc.label_cost(s, gold, label) @staticmethod @@ -204,23 +205,23 @@ cdef class LeftArc: return cost + pop_cost(s, gold, s.S(0)) + arc_cost(s, gold, s.B(0), s.S(0)) @staticmethod - cdef inline weight_t label_cost(StateClass s, const GoldParseC* gold, int label) nogil: + cdef inline weight_t label_cost(StateClass s, const GoldParseC* gold, attr_t label) nogil: return arc_is_gold(gold, s.B(0), s.S(0)) and not label_is_gold(gold, s.B(0), s.S(0), label) cdef class RightArc: @staticmethod - cdef bint is_valid(const StateC* st, int label) nogil: + cdef bint is_valid(const StateC* st, attr_t label) nogil: return not st.B_(0).sent_start @staticmethod - cdef int transition(StateC* st, int label) nogil: + cdef int transition(StateC* st, attr_t label) nogil: st.add_arc(st.S(0), st.B(0), label) st.push() st.fast_forward() @staticmethod - cdef inline weight_t cost(StateClass s, const GoldParseC* gold, int label) nogil: + cdef inline weight_t cost(StateClass s, const GoldParseC* gold, attr_t label) nogil: return RightArc.move_cost(s, gold) + RightArc.label_cost(s, gold, label) @staticmethod @@ -233,13 +234,13 @@ cdef class RightArc: return push_cost(s, gold, s.B(0)) + arc_cost(s, gold, s.S(0), s.B(0)) @staticmethod - cdef weight_t label_cost(StateClass s, const GoldParseC* gold, int label) nogil: + cdef weight_t label_cost(StateClass s, const GoldParseC* gold, attr_t label) nogil: return arc_is_gold(gold, s.S(0), s.B(0)) and not label_is_gold(gold, s.S(0), s.B(0), label) cdef class Break: @staticmethod - cdef bint is_valid(const StateC* st, int label) nogil: + cdef bint is_valid(const StateC* st, attr_t label) nogil: cdef int i if not USE_BREAK: return False @@ -251,12 +252,12 @@ cdef class Break: return True @staticmethod - cdef int transition(StateC* st, int label) nogil: + cdef int transition(StateC* st, attr_t label) nogil: st.set_break(st.B_(0).l_edge) st.fast_forward() @staticmethod - cdef weight_t cost(StateClass s, const GoldParseC* gold, int label) nogil: + cdef weight_t cost(StateClass s, const GoldParseC* gold, attr_t label) nogil: return Break.move_cost(s, gold) + Break.label_cost(s, gold, label) @staticmethod @@ -281,13 +282,13 @@ cdef class Break: return cost + 1 @staticmethod - cdef inline weight_t label_cost(StateClass s, const GoldParseC* gold, int label) nogil: + cdef inline weight_t label_cost(StateClass s, const GoldParseC* gold, attr_t label) nogil: return 0 cdef int _get_root(int word, const GoldParseC* gold) nogil: - while gold.heads[word] != word and gold.labels[word] != -1 and word >= 0: + while gold.heads[word] != word and not gold.has_dep[word] and word >= 0: word = gold.heads[word] - if gold.labels[word] == -1: + if not gold.has_dep[word]: return -1 else: return word @@ -295,9 +296,7 @@ cdef int _get_root(int word, const GoldParseC* gold) nogil: cdef void* _init_state(Pool mem, int length, void* tokens) except NULL: cdef StateClass st = StateClass.init(tokens, length) - # Ensure sent_start is set to 0 throughout for i in range(st.c.length): - st.c._sent[i].sent_start = False st.c._sent[i].l_edge = i st.c._sent[i].r_edge = i st.fast_forward() @@ -313,21 +312,24 @@ cdef class ArcEager(TransitionSystem): @classmethod def get_actions(cls, **kwargs): actions = kwargs.get('actions', - { - SHIFT: [''], - REDUCE: [''], - RIGHT: [], - LEFT: [], - BREAK: ['ROOT']}) + OrderedDict(( + (SHIFT, ['']), + (REDUCE, ['']), + (RIGHT, []), + (LEFT, []), + (BREAK, ['ROOT']) + ))) seen_actions = set() for label in kwargs.get('left_labels', []): if label.upper() != 'ROOT': if (LEFT, label) not in seen_actions: actions[LEFT].append(label) + seen_actions.add((LEFT, label)) for label in kwargs.get('right_labels', []): if label.upper() != 'ROOT': if (RIGHT, label) not in seen_actions: actions[RIGHT].append(label) + seen_actions.add((RIGHT, label)) for raw_text, sents in kwargs.get('gold_parses', []): for (ids, words, tags, heads, labels, iob), ctnts in sents: @@ -338,29 +340,39 @@ cdef class ArcEager(TransitionSystem): if head < child: if (RIGHT, label) not in seen_actions: actions[RIGHT].append(label) + seen_actions.add((RIGHT, label)) elif head > child: if (LEFT, label) not in seen_actions: actions[LEFT].append(label) + seen_actions.add((LEFT, label)) return actions property action_types: def __get__(self): return (SHIFT, REDUCE, LEFT, RIGHT, BREAK) - cdef int preprocess_gold(self, GoldParse gold) except -1: + def has_gold(self, GoldParse gold, start=0, end=None): + end = end or len(gold.heads) + if all([tag is None for tag in gold.heads[start:end]]): + return False + else: + return True + + def preprocess_gold(self, GoldParse gold): + if not self.has_gold(gold): + return None for i in range(gold.length): - if gold.heads[i] is None: # Missing values + if gold.heads[i] is None or gold.labels[i] is None: # Missing values gold.c.heads[i] = i - gold.c.labels[i] = -1 + gold.c.has_dep[i] = False else: label = gold.labels[i] + gold.c.has_dep[i] = True if label.upper() == 'ROOT': label = 'ROOT' gold.c.heads[i] = gold.heads[i] - gold.c.labels[i] = self.strings[label] - # Count frequencies, for use in encoder - self.freqs[HEAD][gold.c.heads[i] - i] += 1 - self.freqs[DEP][gold.c.labels[i]] += 1 + gold.c.labels[i] = self.strings.add(label) + return gold cdef Transition lookup_transition(self, object name) except *: if '-' in name: @@ -373,15 +385,16 @@ cdef class ArcEager(TransitionSystem): for i in range(self.n_moves): if self.c[i].move == move and self.c[i].label == label: return self.c[i] + return Transition(clas=0, move=MISSING, label=0) - def move_name(self, int move, int label): + def move_name(self, int move, attr_t label): label_str = self.strings[label] if label_str: return MOVE_NAMES[move] + '-' + label_str else: return MOVE_NAMES[move] - cdef Transition init_transition(self, int clas, int move, int label) except *: + cdef Transition init_transition(self, int clas, int move, attr_t label) except *: # TODO: Apparent Cython bug here when we try to use the Transition() # constructor with the function pointers cdef Transition t @@ -414,9 +427,7 @@ cdef class ArcEager(TransitionSystem): return t cdef int initialize_state(self, StateC* st) nogil: - # Ensure sent_start is set to 0 throughout for i in range(st.length): - st._sent[i].sent_start = False st._sent[i].l_edge = i st._sent[i].r_edge = i st.fast_forward() @@ -432,18 +443,19 @@ cdef class ArcEager(TransitionSystem): cdef int set_valid(self, int* output, const StateC* st) nogil: cdef bint[N_MOVES] is_valid - is_valid[SHIFT] = Shift.is_valid(st, -1) - is_valid[REDUCE] = Reduce.is_valid(st, -1) - is_valid[LEFT] = LeftArc.is_valid(st, -1) - is_valid[RIGHT] = RightArc.is_valid(st, -1) - is_valid[BREAK] = Break.is_valid(st, -1) + is_valid[SHIFT] = Shift.is_valid(st, 0) + is_valid[REDUCE] = Reduce.is_valid(st, 0) + is_valid[LEFT] = LeftArc.is_valid(st, 0) + is_valid[RIGHT] = RightArc.is_valid(st, 0) + is_valid[BREAK] = Break.is_valid(st, 0) cdef int i for i in range(self.n_moves): output[i] = is_valid[self.c[i].move] cdef int set_costs(self, int* is_valid, weight_t* costs, StateClass stcls, GoldParse gold) except -1: - cdef int i, move, label + cdef int i, move + cdef attr_t label cdef label_cost_func_t[N_MOVES] label_cost_funcs cdef move_cost_func_t[N_MOVES] move_cost_funcs cdef weight_t[N_MOVES] move_costs @@ -461,7 +473,7 @@ cdef class ArcEager(TransitionSystem): label_cost_funcs[RIGHT] = RightArc.label_cost label_cost_funcs[BREAK] = Break.label_cost - cdef int* labels = gold.c.labels + cdef attr_t* labels = gold.c.labels cdef int* heads = gold.c.heads n_gold = 0 @@ -501,3 +513,23 @@ cdef class ArcEager(TransitionSystem): "State at failure:\n" "%s" % (self.n_moves, stcls.print_state(gold.words))) assert n_gold >= 1 + + def get_beam_annot(self, Beam beam): + length = (beam.at(0)).c.length + heads = [{} for _ in range(length)] + deps = [{} for _ in range(length)] + probs = beam.probs + for i in range(beam.size): + stcls = beam.at(i) + self.finalize_state(stcls.c) + if stcls.is_final(): + prob = probs[i] + for j in range(stcls.c.length): + head = j + stcls.c._sent[j].head + dep = stcls.c._sent[j].dep + heads[j].setdefault(head, 0.0) + heads[j][head] += prob + deps[j].setdefault(dep, 0.0) + deps[j][dep] += prob + return heads, deps + diff --git a/spacy/syntax/iterators.pyx b/spacy/syntax/iterators.pyx index e1c44da7f..557616d18 100644 --- a/spacy/syntax/iterators.pyx +++ b/spacy/syntax/iterators.pyx @@ -1,7 +1,7 @@ # coding: utf-8 from __future__ import unicode_literals -from ..parts_of_speech cimport NOUN, PROPN, PRON +from ..parts_of_speech cimport NOUN, PROPN, PRON, VERB, AUX def english_noun_chunks(obj): @@ -12,9 +12,9 @@ def english_noun_chunks(obj): labels = ['nsubj', 'dobj', 'nsubjpass', 'pcomp', 'pobj', 'attr', 'ROOT'] doc = obj.doc # Ensure works on both Doc and Span. - np_deps = [doc.vocab.strings[label] for label in labels] - conj = doc.vocab.strings['conj'] - np_label = doc.vocab.strings['NP'] + np_deps = [doc.vocab.strings.add(label) for label in labels] + conj = doc.vocab.strings.add('conj') + np_label = doc.vocab.strings.add('NP') seen = set() for i, word in enumerate(obj): if word.pos not in (NOUN, PROPN, PRON): @@ -48,9 +48,9 @@ def english_noun_chunks(obj): def german_noun_chunks(obj): labels = ['sb', 'oa', 'da', 'nk', 'mo', 'ag', 'ROOT', 'root', 'cj', 'pd', 'og', 'app'] doc = obj.doc # Ensure works on both Doc and Span. - np_label = doc.vocab.strings['NP'] - np_deps = set(doc.vocab.strings[label] for label in labels) - close_app = doc.vocab.strings['nk'] + np_label = doc.vocab.strings.add('NP') + np_deps = set(doc.vocab.strings.add(label) for label in labels) + close_app = doc.vocab.strings.add('nk') rbracket = 0 for i, word in enumerate(obj): @@ -66,4 +66,79 @@ def german_noun_chunks(obj): yield word.left_edge.i, rbracket, np_label -CHUNKERS = {'en': english_noun_chunks, 'de': german_noun_chunks} +def es_noun_chunks(obj): + doc = obj.doc + np_label = doc.vocab.strings['NP'] + left_labels = ['det', 'fixed', 'neg'] #['nunmod', 'det', 'appos', 'fixed'] + right_labels = ['flat', 'fixed', 'compound', 'neg'] + stop_labels = ['punct'] + np_left_deps = [doc.vocab.strings[label] for label in left_labels] + np_right_deps = [doc.vocab.strings[label] for label in right_labels] + stop_deps = [doc.vocab.strings[label] for label in stop_labels] + + def next_token(token): + try: + return token.nbor() + except: + return None + + def noun_bounds(root): + def is_verb_token(token): + return token.pos in [VERB, AUX] + + left_bound = root + for token in reversed(list(root.lefts)): + if token.dep in np_left_deps: + left_bound = token + right_bound = root + for token in root.rights: + if (token.dep in np_right_deps): + left, right = noun_bounds(token) + if list(filter(lambda t: is_verb_token(t) or t.dep in stop_deps, + doc[left_bound.i: right.i])): + break + else: + right_bound = right + return left_bound, right_bound + + token = doc[0] + while token and token.i < len(doc): + if token.pos in [PROPN, NOUN, PRON]: + left, right = noun_bounds(token) + yield left.i, right.i+1, np_label + token = right + token = next_token(token) + + +def french_noun_chunks(obj): + labels = ['nsubj', 'nsubj:pass', 'obj', 'iobj', 'ROOT', 'appos', 'nmod', 'nmod:poss'] + doc = obj.doc # Ensure works on both Doc and Span. + np_deps = [doc.vocab.strings[label] for label in labels] + conj = doc.vocab.strings.add('conj') + np_label = doc.vocab.strings.add('NP') + seen = set() + for i, word in enumerate(obj): + if word.pos not in (NOUN, PROPN, PRON): + continue + # Prevent nested chunks from being produced + if word.i in seen: + continue + if word.dep in np_deps: + if any(w.i in seen for w in word.subtree): + continue + seen.update(j for j in range(word.left_edge.i, word.right_edge.i+1)) + yield word.left_edge.i, word.right_edge.i+1, np_label + elif word.dep == conj: + head = word.head + while head.dep == conj and head.head.i < head.i: + head = head.head + # If the head is an NP, and we're coordinated to it, we're an NP + if head.dep in np_deps: + if any(w.i in seen for w in word.subtree): + continue + seen.update(j for j in range(word.left_edge.i, word.right_edge.i+1)) + yield word.left_edge.i, word.right_edge.i+1, np_label + + +CHUNKERS = {'en': english_noun_chunks, 'de': german_noun_chunks, + 'es': es_noun_chunks, 'fr': french_noun_chunks} diff --git a/spacy/syntax/ner.pxd b/spacy/syntax/ner.pxd index 0e3403230..647f98fc0 100644 --- a/spacy/syntax/ner.pxd +++ b/spacy/syntax/ner.pxd @@ -1,6 +1,7 @@ from .transition_system cimport TransitionSystem from .transition_system cimport Transition from ..gold cimport GoldParseC +from ..typedefs cimport attr_t cdef class BiluoPushDown(TransitionSystem): diff --git a/spacy/syntax/ner.pyx b/spacy/syntax/ner.pyx index 2758c242c..d15de0181 100644 --- a/spacy/syntax/ner.pyx +++ b/spacy/syntax/ner.pyx @@ -2,6 +2,10 @@ from __future__ import unicode_literals from thinc.typedefs cimport weight_t +from thinc.extra.search cimport Beam +from collections import OrderedDict +import numpy +from thinc.neural.ops import NumpyOps from .stateclass cimport StateClass from ._state cimport StateC @@ -51,17 +55,29 @@ cdef bint _entity_is_sunk(StateClass st, Transition* golds) nogil: cdef class BiluoPushDown(TransitionSystem): + def __init__(self, *args, **kwargs): + TransitionSystem.__init__(self, *args, **kwargs) + + def __reduce__(self): + labels_by_action = OrderedDict() + cdef Transition t + for trans in self.c[:self.n_moves]: + label_str = self.strings[trans.label] + labels_by_action.setdefault(trans.move, []).append(label_str) + return (BiluoPushDown, (self.strings, labels_by_action), + None, None) + @classmethod def get_actions(cls, **kwargs): actions = kwargs.get('actions', - { - MISSING: [''], - BEGIN: [], - IN: [], - LAST: [], - UNIT: [], - OUT: [''] - }) + OrderedDict(( + (MISSING, ['']), + (BEGIN, []), + (IN, []), + (LAST, []), + (UNIT, []), + (OUT, ['']) + ))) seen_entities = set() for entity_type in kwargs.get('entity_types', []): if entity_type in seen_entities: @@ -87,42 +103,75 @@ cdef class BiluoPushDown(TransitionSystem): def __get__(self): return (BEGIN, IN, LAST, UNIT, OUT) - def move_name(self, int move, int label): + def move_name(self, int move, attr_t label): if move == OUT: return 'O' - elif move == 'MISSING': + elif move == MISSING: return 'M' else: return MOVE_NAMES[move] + '-' + self.strings[label] - cdef int preprocess_gold(self, GoldParse gold) except -1: + def has_gold(self, GoldParse gold, start=0, end=None): + end = end or len(gold.ner) + if all([tag == '-' for tag in gold.ner[start:end]]): + return False + else: + return True + + def preprocess_gold(self, GoldParse gold): + if not self.has_gold(gold): + return None for i in range(gold.length): gold.c.ner[i] = self.lookup_transition(gold.ner[i]) - # Count frequencies, for use in encoder - if gold.c.ner[i].move in (BEGIN, UNIT): - self.freqs[ENT_IOB][3] += 1 - self.freqs[ENT_TYPE][gold.c.ner[i].label] += 1 - elif gold.c.ner[i].move in (IN, LAST): - self.freqs[ENT_IOB][2] += 1 - self.freqs[ENT_TYPE][0] += 1 - elif gold.c.ner[i].move == OUT: - self.freqs[ENT_IOB][1] += 1 - self.freqs[ENT_TYPE][0] += 1 - else: - self.freqs[ENT_IOB][1] += 1 - self.freqs[ENT_TYPE][0] += 1 + return gold + + def get_beam_annot(self, Beam beam): + entities = {} + probs = beam.probs + for i in range(beam.size): + stcls = beam.at(i) + if stcls.is_final(): + self.finalize_state(stcls.c) + prob = probs[i] + for j in range(stcls.c._e_i): + start = stcls.c._ents[j].start + end = stcls.c._ents[j].end + label = stcls.c._ents[j].label + entities.setdefault((start, end, label), 0.0) + entities[(start, end, label)] += prob + return entities + + def get_beam_parses(self, Beam beam): + parses = [] + probs = beam.probs + for i in range(beam.size): + stcls = beam.at(i) + if stcls.is_final(): + self.finalize_state(stcls.c) + prob = probs[i] + parse = [] + for j in range(stcls.c._e_i): + start = stcls.c._ents[j].start + end = stcls.c._ents[j].end + label = stcls.c._ents[j].label + parse.append((start, end, self.strings[label])) + parses.append((prob, parse)) + return parses cdef Transition lookup_transition(self, object name) except *: + cdef attr_t label if name == '-' or name == None: move_str = 'M' label = 0 + elif name == '!O': + return Transition(clas=0, move=ISNT, label=0, score=0) elif '-' in name: move_str, label_str = name.split('-', 1) # Hacky way to denote 'not this entity' if label_str.startswith('!'): label_str = label_str[1:] move_str = 'x' - label = self.strings[label_str] + label = self.strings.add(label_str) else: move_str = name label = 0 @@ -135,7 +184,7 @@ cdef class BiluoPushDown(TransitionSystem): else: raise KeyError(name) - cdef Transition init_transition(self, int clas, int move, int label) except *: + cdef Transition init_transition(self, int clas, int move, attr_t label) except *: # TODO: Apparent Cython bug here when we try to use the Transition() # constructor with the function pointers cdef Transition t @@ -184,21 +233,21 @@ cdef class BiluoPushDown(TransitionSystem): cdef class Missing: @staticmethod - cdef bint is_valid(const StateC* st, int label) nogil: + cdef bint is_valid(const StateC* st, attr_t label) nogil: return False @staticmethod - cdef int transition(StateC* s, int label) nogil: + cdef int transition(StateC* s, attr_t label) nogil: pass @staticmethod - cdef weight_t cost(StateClass s, const GoldParseC* gold, int label) nogil: + cdef weight_t cost(StateClass s, const GoldParseC* gold, attr_t label) nogil: return 9000 cdef class Begin: @staticmethod - cdef bint is_valid(const StateC* st, int label) nogil: + cdef bint is_valid(const StateC* st, attr_t label) nogil: # Ensure we don't clobber preset entities. If no entity preset, # ent_iob is 0 cdef int preset_ent_iob = st.B_(0).ent_iob @@ -222,16 +271,16 @@ cdef class Begin: return label != 0 and not st.entity_is_open() @staticmethod - cdef int transition(StateC* st, int label) nogil: + cdef int transition(StateC* st, attr_t label) nogil: st.open_ent(label) st.set_ent_tag(st.B(0), 3, label) st.push() st.pop() @staticmethod - cdef weight_t cost(StateClass s, const GoldParseC* gold, int label) nogil: + cdef weight_t cost(StateClass s, const GoldParseC* gold, attr_t label) nogil: cdef int g_act = gold.ner[s.B(0)].move - cdef int g_tag = gold.ner[s.B(0)].label + cdef attr_t g_tag = gold.ner[s.B(0)].label if g_act == MISSING: return 0 @@ -251,7 +300,7 @@ cdef class Begin: cdef class In: @staticmethod - cdef bint is_valid(const StateC* st, int label) nogil: + cdef bint is_valid(const StateC* st, attr_t label) nogil: cdef int preset_ent_iob = st.B_(0).ent_iob if preset_ent_iob == 2: return False @@ -267,17 +316,17 @@ cdef class In: return st.entity_is_open() and label != 0 and st.E_(0).ent_type == label @staticmethod - cdef int transition(StateC* st, int label) nogil: + cdef int transition(StateC* st, attr_t label) nogil: st.set_ent_tag(st.B(0), 1, label) st.push() st.pop() @staticmethod - cdef weight_t cost(StateClass s, const GoldParseC* gold, int label) nogil: + cdef weight_t cost(StateClass s, const GoldParseC* gold, attr_t label) nogil: move = IN cdef int next_act = gold.ner[s.B(1)].move if s.B(0) < s.c.length else OUT cdef int g_act = gold.ner[s.B(0)].move - cdef int g_tag = gold.ner[s.B(0)].label + cdef attr_t g_tag = gold.ner[s.B(0)].label cdef bint is_sunk = _entity_is_sunk(s, gold.ner) if g_act == MISSING: @@ -297,30 +346,33 @@ cdef class In: elif g_act == UNIT: # I, Gold U --> True iff next tag == O return next_act != OUT + # Support partial supervision in the form of "not this label" + elif g_act == ISNT: + return 0 else: return 1 cdef class Last: @staticmethod - cdef bint is_valid(const StateC* st, int label) nogil: + cdef bint is_valid(const StateC* st, attr_t label) nogil: if st.B_(1).ent_iob == 1: return False return st.entity_is_open() and label != 0 and st.E_(0).ent_type == label @staticmethod - cdef int transition(StateC* st, int label) nogil: + cdef int transition(StateC* st, attr_t label) nogil: st.close_ent() st.set_ent_tag(st.B(0), 1, label) st.push() st.pop() @staticmethod - cdef weight_t cost(StateClass s, const GoldParseC* gold, int label) nogil: + cdef weight_t cost(StateClass s, const GoldParseC* gold, attr_t label) nogil: move = LAST cdef int g_act = gold.ner[s.B(0)].move - cdef int g_tag = gold.ner[s.B(0)].label + cdef attr_t g_tag = gold.ner[s.B(0)].label if g_act == MISSING: return 0 @@ -339,13 +391,16 @@ cdef class Last: elif g_act == UNIT: # L, Gold U --> True return 0 + # Support partial supervision in the form of "not this label" + elif g_act == ISNT: + return 0 else: return 1 cdef class Unit: @staticmethod - cdef bint is_valid(const StateC* st, int label) nogil: + cdef bint is_valid(const StateC* st, attr_t label) nogil: cdef int preset_ent_iob = st.B_(0).ent_iob if preset_ent_iob == 2: return False @@ -358,7 +413,7 @@ cdef class Unit: return label != 0 and not st.entity_is_open() @staticmethod - cdef int transition(StateC* st, int label) nogil: + cdef int transition(StateC* st, attr_t label) nogil: st.open_ent(label) st.close_ent() st.set_ent_tag(st.B(0), 3, label) @@ -366,9 +421,9 @@ cdef class Unit: st.pop() @staticmethod - cdef weight_t cost(StateClass s, const GoldParseC* gold, int label) nogil: + cdef weight_t cost(StateClass s, const GoldParseC* gold, attr_t label) nogil: cdef int g_act = gold.ner[s.B(0)].move - cdef int g_tag = gold.ner[s.B(0)].label + cdef attr_t g_tag = gold.ner[s.B(0)].label if g_act == MISSING: return 0 @@ -388,7 +443,7 @@ cdef class Unit: cdef class Out: @staticmethod - cdef bint is_valid(const StateC* st, int label) nogil: + cdef bint is_valid(const StateC* st, attr_t label) nogil: cdef int preset_ent_iob = st.B_(0).ent_iob if preset_ent_iob == 3: return False @@ -397,17 +452,19 @@ cdef class Out: return not st.entity_is_open() @staticmethod - cdef int transition(StateC* st, int label) nogil: + cdef int transition(StateC* st, attr_t label) nogil: st.set_ent_tag(st.B(0), 2, 0) st.push() st.pop() @staticmethod - cdef weight_t cost(StateClass s, const GoldParseC* gold, int label) nogil: + cdef weight_t cost(StateClass s, const GoldParseC* gold, attr_t label) nogil: cdef int g_act = gold.ner[s.B(0)].move - cdef int g_tag = gold.ner[s.B(0)].label + cdef attr_t g_tag = gold.ner[s.B(0)].label - if g_act == MISSING or g_act == ISNT: + if g_act == ISNT and g_tag == 0: + return 1 + elif g_act == MISSING or g_act == ISNT: return 0 elif g_act == BEGIN: # O, Gold B --> False diff --git a/spacy/syntax/nn_parser.pyx b/spacy/syntax/nn_parser.pyx index 2e6687730..06c61656b 100644 --- a/spacy/syntax/nn_parser.pyx +++ b/spacy/syntax/nn_parser.pyx @@ -5,7 +5,7 @@ # coding: utf-8 from __future__ import unicode_literals, print_function -from collections import Counter +from collections import Counter, OrderedDict import ujson import contextlib @@ -18,6 +18,7 @@ import dill import numpy.random cimport numpy as np +from libcpp.vector cimport vector from cpython.ref cimport PyObject, Py_INCREF, Py_XDECREF from cpython.exc cimport PyErr_CheckSignals from libc.stdint cimport uint32_t, uint64_t @@ -28,26 +29,30 @@ from thinc.linear.avgtron cimport AveragedPerceptron from thinc.linalg cimport VecVec from thinc.structs cimport SparseArrayC, FeatureC, ExampleC from thinc.extra.eg cimport Example +from thinc.extra.search cimport Beam + from cymem.cymem cimport Pool, Address from murmurhash.mrmr cimport hash64 from preshed.maps cimport MapStruct from preshed.maps cimport map_get -from thinc.api import layerize, chain +from thinc.api import layerize, chain, noop, clone from thinc.neural import Model, Affine, ELU, ReLu, Maxout -from thinc.neural.ops import NumpyOps +from thinc.neural.ops import NumpyOps, CupyOps +from thinc.neural.util import get_array_module from .. import util from ..util import get_async, get_cuda_stream from .._ml import zero_init, PrecomputableAffine, PrecomputableMaxouts -from .._ml import Tok2Vec, doc2feats +from .._ml import Tok2Vec, doc2feats, rebatch +from ..compat import json_dumps from . import _parse_features from ._parse_features cimport CONTEXT_SIZE from ._parse_features cimport fill_context from .stateclass cimport StateClass from ._state cimport StateC -from .nonproj import PseudoProjectivity +from . import nonproj from .transition_system import OracleError from .transition_system cimport TransitionSystem, Transition from ..structs cimport TokenC @@ -104,68 +109,75 @@ cdef class precompute_hiddens: cached = gpu_cached self.nF = cached.shape[1] self.nO = cached.shape[2] - self.nP = cached.shape[3] + self.nP = getattr(lower_model, 'nP', 1) self.ops = lower_model.ops - self._features = numpy.zeros((batch_size, self.nO, self.nP), dtype='f') self._is_synchronized = False self._cuda_stream = cuda_stream self._cached = cached self._bp_hiddens = bp_features - def __call__(self, X): - return self.begin_update(X)[0] - - def begin_update(self, token_ids, drop=0.): - self._features.fill(0) + cdef const float* get_feat_weights(self) except NULL: if not self._is_synchronized \ and self._cuda_stream is not None: self._cuda_stream.synchronize() self._is_synchronized = True + return self._cached.data + + def __call__(self, X): + return self.begin_update(X)[0] + + def begin_update(self, token_ids, drop=0.): + cdef np.ndarray state_vector = numpy.zeros((token_ids.shape[0], self.nO*self.nP), dtype='f') # This is tricky, but (assuming GPU available); # - Input to forward on CPU # - Output from forward on CPU # - Input to backward on GPU! # - Output from backward on GPU - cdef np.ndarray state_vector = self._features[:len(token_ids)] - cdef np.ndarray hiddens = self._cached bp_hiddens = self._bp_hiddens + feat_weights = self.get_feat_weights() cdef int[:, ::1] ids = token_ids - self._sum_features(state_vector.data, - hiddens.data, &ids[0,0], + sum_state_features(state_vector.data, + feat_weights, &ids[0,0], token_ids.shape[0], self.nF, self.nO*self.nP) + state_vector, bp_nonlinearity = self._nonlinearity(state_vector) - output, bp_output = self._apply_nonlinearity(state_vector) - - def backward(d_output, sgd=None): + def backward(d_state_vector, sgd=None): + if bp_nonlinearity is not None: + d_state_vector = bp_nonlinearity(d_state_vector, sgd) # This will usually be on GPU - if isinstance(d_output, numpy.ndarray): - d_output = self.ops.xp.array(d_output) - d_state_vector = bp_output(d_output, sgd) + if isinstance(d_state_vector, numpy.ndarray): + d_state_vector = self.ops.xp.array(d_state_vector) d_tokens = bp_hiddens((d_state_vector, token_ids), sgd) return d_tokens - return output, backward + return state_vector, backward - def _apply_nonlinearity(self, X): - if self.nP < 2: - return X.reshape(X.shape[:2]), lambda dX, sgd=None: dX.reshape(X.shape) - best, which = self.ops.maxout(X) - return best, lambda dX, sgd=None: self.ops.backprop_maxout(dX, which, self.nP) + def _nonlinearity(self, state_vector): + if self.nP == 1: + return state_vector, None + state_vector = state_vector.reshape( + (state_vector.shape[0], state_vector.shape[1]//self.nP, self.nP)) + best, which = self.ops.maxout(state_vector) + def backprop(d_best, sgd=None): + return self.ops.backprop_maxout(d_best, which, self.nP) + return best, backprop - cdef void _sum_features(self, float* output, - const float* cached, const int* token_ids, int B, int F, int O) nogil: - cdef int idx, b, f, i - cdef const float* feature - for b in range(B): - for f in range(F): - if token_ids[f] < 0: - continue - idx = token_ids[f] * F * O + f*O - feature = &cached[idx] - for i in range(O): - output[i] += feature[i] - output += O - token_ids += F + + +cdef void sum_state_features(float* output, + const float* cached, const int* token_ids, int B, int F, int O) nogil: + cdef int idx, b, f, i + cdef const float* feature + for b in range(B): + for f in range(F): + if token_ids[f] < 0: + continue + idx = token_ids[f] * F * O + f*O + feature = &cached[idx] + for i in range(O): + output[i] += feature[i] + output += O + token_ids += F cdef void cpu_log_loss(float* d_scores, @@ -220,25 +232,39 @@ cdef class Parser: Base class of the DependencyParser and EntityRecognizer. """ @classmethod - def Model(cls, nr_class, token_vector_width=128, hidden_width=128, **cfg): + def Model(cls, nr_class, token_vector_width=128, hidden_width=128, depth=1, **cfg): + depth = util.env_opt('parser_hidden_depth', depth) token_vector_width = util.env_opt('token_vector_width', token_vector_width) hidden_width = util.env_opt('hidden_width', hidden_width) - maxout_pieces = util.env_opt('parser_maxout_pieces', 1) - lower = PrecomputableMaxouts(hidden_width, - nF=cls.nr_feature, - nI=token_vector_width, - pieces=maxout_pieces) + parser_maxout_pieces = util.env_opt('parser_maxout_pieces', 2) + tensors = Tok2Vec(token_vector_width, 7500, preprocess=doc2feats()) + if parser_maxout_pieces == 1: + lower = PrecomputableAffine(hidden_width if depth >= 1 else nr_class, + nF=cls.nr_feature, + nI=token_vector_width) + else: + lower = PrecomputableMaxouts(hidden_width if depth >= 1 else nr_class, + nF=cls.nr_feature, + nP=parser_maxout_pieces, + nI=token_vector_width) with Model.use_device('cpu'): upper = chain( - Maxout(hidden_width), - zero_init(Affine(nr_class)) - ) + clone(Maxout(hidden_width), (depth-1)), + zero_init(Affine(nr_class, drop_factor=0.0)) + ) # TODO: This is an unfortunate hack atm! # Used to set input dimensions in network. lower.begin_training(lower.ops.allocate((500, token_vector_width))) upper.begin_training(upper.ops.allocate((500, hidden_width))) - return lower, upper + cfg = { + 'nr_class': nr_class, + 'depth': depth, + 'token_vector_width': token_vector_width, + 'hidden_width': hidden_width, + 'maxout_pieces': parser_maxout_pieces + } + return (tensors, lower, upper), cfg def __init__(self, Vocab vocab, moves=True, model=True, **cfg): """ @@ -274,7 +300,7 @@ cdef class Parser: def __reduce__(self): return (Parser, (self.vocab, self.moves, self.model), None, None) - def __call__(self, Doc tokens, state=None): + def __call__(self, Doc doc, beam_width=None, beam_density=None): """ Apply the parser or entity recognizer, setting the annotations onto the Doc object. @@ -283,10 +309,26 @@ cdef class Parser: Returns: None """ - self.parse_batch([tokens], state['tokvecs']) - return state + if beam_width is None: + beam_width = self.cfg.get('beam_width', 1) + if beam_density is None: + beam_density = self.cfg.get('beam_density', 0.001) + cdef Beam beam + if beam_width == 1: + states = self.parse_batch([doc], [doc.tensor]) + self.set_annotations([doc], states) + return doc + else: + beam = self.beam_parse([doc], [doc.tensor], + beam_width=beam_width, beam_density=beam_density)[0] + output = self.moves.get_beam_annot(beam) + state = beam.at(0) + self.set_annotations([doc], [state]) + _cleanup(beam) + return output - def pipe(self, stream, int batch_size=1000, int n_threads=2): + def pipe(self, docs, int batch_size=1000, int n_threads=2, + beam_width=1, beam_density=0.001): """ Process a stream of documents. @@ -298,99 +340,244 @@ cdef class Parser: The number of threads with which to work on the buffer in parallel. Yields (Doc): Documents, in order. """ - cdef StateClass parse_state cdef Doc doc - queue = [] - for batch in cytoolz.partition_all(batch_size, stream): - batch = list(batch) - docs, states = zip(*batch) - parse_states = self.parse_batch(docs, states[0]['tokvecs']) + for docs in cytoolz.partition_all(batch_size, docs): + docs = list(docs) + tokvecs = [doc.tensor for doc in docs] + if beam_width == 1: + parse_states = self.parse_batch(docs, tokvecs) + else: + parse_states = self.beam_parse(docs, tokvecs, + beam_width=beam_width, beam_density=beam_density) self.set_annotations(docs, parse_states) - yield from zip(docs, states) + yield from docs + + def parse_batch(self, docs, tokvecses): + cdef: + precompute_hiddens state2vec + StateClass state + Pool mem + const float* feat_weights + StateC* st + vector[StateC*] next_step, this_step + int nr_class, nr_feat, nr_piece, nr_dim, nr_state + if isinstance(docs, Doc): + docs = [docs] + if isinstance(tokvecses, np.ndarray): + tokvecses = [tokvecses] + + tokvecs = self.model[0].ops.flatten(tokvecses) + tokvecs += self.model[0].ops.flatten(self.model[0](docs)) + + nr_state = len(docs) + nr_class = self.moves.n_moves + nr_dim = tokvecs.shape[1] + nr_feat = self.nr_feature - def parse_batch(self, docs, tokvecs): cuda_stream = get_cuda_stream() + state2vec, vec2scores = self.get_batch_model(nr_state, tokvecs, + cuda_stream, 0.0) + nr_piece = state2vec.nP states = self.moves.init_batch(docs) - state2vec, vec2scores = self.get_batch_model(len(states), tokvecs, - cuda_stream, 0.0) + for state in states: + if not state.c.is_final(): + next_step.push_back(state.c) - todo = [st for st in states if not st.is_final()] - while todo: - token_ids = self.get_token_ids(states) - vectors = state2vec(token_ids) + feat_weights = state2vec.get_feat_weights() + cdef int i + cdef np.ndarray token_ids = numpy.zeros((nr_state, nr_feat), dtype='i') + cdef np.ndarray is_valid = numpy.zeros((nr_state, nr_class), dtype='i') + cdef np.ndarray scores + c_token_ids = token_ids.data + c_is_valid = is_valid.data + while not next_step.empty(): + for i in range(next_step.size()): + st = next_step[i] + st.set_context_tokens(&c_token_ids[i*nr_feat], nr_feat) + self.moves.set_valid(&c_is_valid[i*nr_class], st) + vectors = state2vec(token_ids[:next_step.size()]) scores = vec2scores(vectors) - self.transition_batch(states, scores) - todo = [st for st in states if not st.is_final()] + c_scores = scores.data + for i in range(next_step.size()): + st = next_step[i] + guess = arg_max_if_valid( + &c_scores[i*nr_class], &c_is_valid[i*nr_class], nr_class) + action = self.moves.c[guess] + action.do(st, action.label) + this_step, next_step = next_step, this_step + next_step.clear() + for st in this_step: + if not st.is_final(): + next_step.push_back(st) return states - def update(self, docs, golds, state=None, drop=0., sgd=None): - assert state is not None - assert 'tokvecs' in state - assert 'bp_tokvecs' in state + def beam_parse(self, docs, tokvecses, int beam_width=8, float beam_density=0.001): + cdef Beam beam + cdef np.ndarray scores + cdef Doc doc + cdef int nr_class = self.moves.n_moves + cdef StateClass stcls, output + tokvecs = self.model[0].ops.flatten(tokvecses) + tokvecs += self.model[0].ops.flatten(self.model[0](docs)) + cuda_stream = get_cuda_stream() + state2vec, vec2scores = self.get_batch_model(len(docs), tokvecs, + cuda_stream, 0.0) + beams = [] + cdef int offset = 0 + for doc in docs: + beam = Beam(nr_class, beam_width, min_density=beam_density) + beam.initialize(self.moves.init_beam_state, doc.length, doc.c) + for i in range(beam.width): + stcls = beam.at(i) + stcls.c.offset = offset + offset += len(doc) + beam.check_done(_check_final_state, NULL) + while not beam.is_done: + states = [] + for i in range(beam.size): + stcls = beam.at(i) + states.append(stcls) + token_ids = self.get_token_ids(states) + vectors = state2vec(token_ids) + scores = vec2scores(vectors) + for i in range(beam.size): + stcls = beam.at(i) + if not stcls.is_final(): + self.moves.set_valid(beam.is_valid[i], stcls.c) + for j in range(nr_class): + beam.scores[i][j] = scores[i, j] + beam.advance(_transition_state, _hash_state, self.moves.c) + beam.check_done(_check_final_state, NULL) + beams.append(beam) + return beams + + def update(self, docs_tokvecs, golds, drop=0., sgd=None, losses=None): + if losses is not None and self.name not in losses: + losses[self.name] = 0. + docs, tokvec_lists = docs_tokvecs + tokvecs = self.model[0].ops.flatten(tokvec_lists) if isinstance(docs, Doc) and isinstance(golds, GoldParse): docs = [docs] golds = [golds] + my_tokvecs, bp_my_tokvecs = self.model[0].begin_update(docs, drop=0.) + my_tokvecs = self.model[0].ops.flatten(my_tokvecs) + tokvecs += my_tokvecs cuda_stream = get_cuda_stream() - for gold in golds: - self.moves.preprocess_gold(gold) - tokvecs = state['tokvecs'] - bp_tokvecs = state['bp_tokvecs'] - - states = self.moves.init_batch(docs) + states, golds, max_steps = self._init_gold_batch(docs, golds) state2vec, vec2scores = self.get_batch_model(len(states), tokvecs, cuda_stream, - drop) - - todo = [(s, g) for s, g in zip(states, golds) if not s.is_final()] + 0.0) + todo = [(s, g) for (s, g) in zip(states, golds) + if not s.is_final() and g is not None] + if not todo: + return None backprops = [] + d_tokvecs = state2vec.ops.allocate(tokvecs.shape) cdef float loss = 0. - cutoff = max(1, len(todo) // 10) - while len(todo) >= cutoff: + n_steps = 0 + while todo: states, golds = zip(*todo) token_ids = self.get_token_ids(states) - vector, bp_vector = state2vec.begin_update(token_ids, drop=drop) + vector, bp_vector = state2vec.begin_update(token_ids, drop=0.0) + if drop != 0: + mask = vec2scores.ops.get_dropout_mask(vector.shape, drop) + vector *= mask scores, bp_scores = vec2scores.begin_update(vector, drop=drop) d_scores = self.get_batch_loss(states, golds, scores) - d_vector = bp_scores(d_scores, sgd=sgd) - loss += (d_scores**2).sum() + d_vector = bp_scores(d_scores / d_scores.shape[0], sgd=sgd) + if drop != 0: + d_vector *= mask - if not isinstance(tokvecs, state2vec.ops.xp.ndarray): - backprops.append((token_ids, d_vector, bp_vector)) - else: + if isinstance(self.model[0].ops, CupyOps) \ + and not isinstance(token_ids, state2vec.ops.xp.ndarray): # Move token_ids and d_vector to CPU, asynchronously backprops.append(( get_async(cuda_stream, token_ids), get_async(cuda_stream, d_vector), bp_vector )) + else: + backprops.append((token_ids, d_vector, bp_vector)) self.transition_batch(states, scores) todo = [st for st in todo if not st[0].is_final()] + if losses is not None: + losses[self.name] += (d_scores**2).sum() + n_steps += 1 + if n_steps >= max_steps: + break + self._make_updates(d_tokvecs, + backprops, sgd, cuda_stream) + d_tokvecs = self.model[0].ops.unflatten(d_tokvecs, [len(d) for d in docs]) + #bp_my_tokvecs(d_tokvecs, sgd=sgd) + return d_tokvecs + + def _init_gold_batch(self, whole_docs, whole_golds): + """Make a square batch, of length equal to the shortest doc. A long + doc will get multiple states. Let's say we have a doc of length 2*N, + where N is the shortest doc. We'll make two states, one representing + long_doc[:N], and another representing long_doc[N:].""" + cdef: + StateClass state + Transition action + whole_states = self.moves.init_batch(whole_docs) + max_length = max(5, min(50, min([len(doc) for doc in whole_docs]))) + max_moves = 0 + states = [] + golds = [] + for doc, state, gold in zip(whole_docs, whole_states, whole_golds): + gold = self.moves.preprocess_gold(gold) + if gold is None: + continue + oracle_actions = self.moves.get_oracle_sequence(doc, gold) + start = 0 + while start < len(doc): + state = state.copy() + n_moves = 0 + while state.B(0) < start and not state.is_final(): + action = self.moves.c[oracle_actions.pop(0)] + action.do(state.c, action.label) + n_moves += 1 + has_gold = self.moves.has_gold(gold, start=start, + end=start+max_length) + if not state.is_final() and has_gold: + states.append(state) + golds.append(gold) + max_moves = max(max_moves, n_moves) + start += min(max_length, len(doc)-start) + max_moves = max(max_moves, len(oracle_actions)) + return states, golds, max_moves + + def _make_updates(self, d_tokvecs, backprops, sgd, cuda_stream=None): # Tells CUDA to block, so our async copies complete. if cuda_stream is not None: cuda_stream.synchronize() - d_tokvecs = state2vec.ops.allocate(tokvecs.shape) - xp = state2vec.ops.xp # Handle for numpy/cupy - for token_ids, d_vector, bp_vector in backprops: + xp = get_array_module(d_tokvecs) + for ids, d_vector, bp_vector in backprops: d_state_features = bp_vector(d_vector, sgd=sgd) - active_feats = token_ids * (token_ids >= 0) - active_feats = active_feats.reshape((token_ids.shape[0], token_ids.shape[1], 1)) + active_feats = ids * (ids >= 0) + active_feats = active_feats.reshape((ids.shape[0], ids.shape[1], 1)) if hasattr(xp, 'scatter_add'): xp.scatter_add(d_tokvecs, - token_ids, d_state_features * active_feats) + ids, d_state_features * active_feats) else: xp.add.at(d_tokvecs, - token_ids, d_state_features * active_feats) - bp_tokvecs(d_tokvecs, sgd) - state['parser_loss'] = loss - return state + ids, d_state_features * active_feats) + + @property + def move_names(self): + names = [] + for i in range(self.moves.n_moves): + name = self.moves.move_name(self.moves.c[i].move, self.moves.c[i].label) + names.append(name) + return names def get_batch_model(self, batch_size, tokvecs, stream, dropout): - lower, upper = self.model + _, lower, upper = self.model state2vec = precompute_hiddens(batch_size, tokvecs, lower, stream, drop=dropout) return state2vec, upper @@ -400,9 +587,13 @@ cdef class Parser: def get_token_ids(self, states): cdef StateClass state cdef int n_tokens = self.nr_feature - ids = numpy.zeros((len(states), n_tokens), dtype='i', order='C') + cdef np.ndarray ids = numpy.zeros((len(states), n_tokens), + dtype='i', order='C') + c_ids = ids.data for i, state in enumerate(states): - state.set_context_tokens(ids[i]) + if not state.is_final(): + state.c.set_context_tokens(c_ids, n_tokens) + c_ids += ids.shape[1] return ids def transition_batch(self, states, float[:, ::1] scores): @@ -445,7 +636,6 @@ cdef class Parser: self.moves.finalize_doc(doc) def add_label(self, label): - # Doesn't set label into serializer -- subclasses override it to do that. for action in self.moves.action_types: added = self.moves.add_action(action, label) if added: @@ -456,12 +646,18 @@ cdef class Parser: def begin_training(self, gold_tuples, **cfg): if 'model' in cfg: self.model = cfg['model'] + gold_tuples = nonproj.preprocess_training_data(gold_tuples) actions = self.moves.get_actions(gold_parses=gold_tuples) for action, labels in actions.items(): for label in labels: self.moves.add_action(action, label) if self.model is True: - self.model = self.Model(self.moves.n_moves, **cfg) + self.model, cfg = self.Model(self.moves.n_moves, **cfg) + self.cfg.update(cfg) + + def preprocess_gold(self, docs_golds): + for doc, gold in docs_golds: + yield doc, gold def use_params(self, params): # Can't decorate cdef class :(. Workaround. @@ -469,21 +665,85 @@ cdef class Parser: with self.model[1].use_params(params): yield - def to_disk(self, path): - path = util.ensure_path(path) - with (path / 'model.bin').open('wb') as file_: - dill.dump(self.model, file_) + def to_disk(self, path, **exclude): + serializers = { + 'tok2vec_model': lambda p: p.open('wb').write( + self.model[0].to_bytes()), + 'lower_model': lambda p: p.open('wb').write( + self.model[1].to_bytes()), + 'upper_model': lambda p: p.open('wb').write( + self.model[2].to_bytes()), + 'vocab': lambda p: self.vocab.to_disk(p), + 'moves': lambda p: self.moves.to_disk(p, strings=False), + 'cfg': lambda p: p.open('w').write(json_dumps(self.cfg)) + } + util.to_disk(path, serializers, exclude) - def from_disk(self, path): - path = util.ensure_path(path) - with (path / 'model.bin').open('wb') as file_: - self.model = dill.load(file_) + def from_disk(self, path, **exclude): + deserializers = { + 'vocab': lambda p: self.vocab.from_disk(p), + 'moves': lambda p: self.moves.from_disk(p, strings=False), + 'cfg': lambda p: self.cfg.update(ujson.load(p.open())), + 'model': lambda p: None + } + util.from_disk(path, deserializers, exclude) + if 'model' not in exclude: + path = util.ensure_path(path) + if self.model is True: + self.model, cfg = self.Model(**self.cfg) + else: + cfg = {} + with (path / 'tok2vec_model').open('rb') as file_: + bytes_data = file_.read() + self.model[0].from_bytes(bytes_data) + with (path / 'lower_model').open('rb') as file_: + bytes_data = file_.read() + self.model[1].from_bytes(bytes_data) + with (path / 'upper_model').open('rb') as file_: + bytes_data = file_.read() + self.model[2].from_bytes(bytes_data) + self.cfg.update(cfg) + return self - def to_bytes(self): - pass + def to_bytes(self, **exclude): + serializers = OrderedDict(( + ('tok2vec_model', lambda: self.model[0].to_bytes()), + ('lower_model', lambda: self.model[1].to_bytes()), + ('upper_model', lambda: self.model[2].to_bytes()), + ('vocab', lambda: self.vocab.to_bytes()), + ('moves', lambda: self.moves.to_bytes(strings=False)), + ('cfg', lambda: ujson.dumps(self.cfg)) + )) + if 'model' in exclude: + exclude['tok2vec_model'] = True + exclude['lower_model'] = True + exclude['upper_model'] = True + exclude.pop('model') + return util.to_bytes(serializers, exclude) - def from_bytes(self, data): - pass + def from_bytes(self, bytes_data, **exclude): + deserializers = OrderedDict(( + ('vocab', lambda b: self.vocab.from_bytes(b)), + ('moves', lambda b: self.moves.from_bytes(b, strings=False)), + ('cfg', lambda b: self.cfg.update(ujson.loads(b))), + ('tok2vec_model', lambda b: None), + ('lower_model', lambda b: None), + ('upper_model', lambda b: None) + )) + msg = util.from_bytes(bytes_data, deserializers, exclude) + if 'model' not in exclude: + if self.model is True: + self.model, cfg = self.Model(self.moves.n_moves) + else: + cfg = {} + if 'tok2vec_model' in msg: + self.model[0].from_bytes(msg['tok2vec_model']) + if 'lower_model' in msg: + self.model[1].from_bytes(msg['lower_model']) + if 'upper_model' in msg: + self.model[2].from_bytes(msg['upper_model']) + self.cfg.update(cfg) + return self class ParserStateError(ValueError): @@ -521,6 +781,19 @@ cdef int arg_max_if_valid(const weight_t* scores, const int* is_valid, int n) no return best +cdef int arg_maxout_if_valid(const weight_t* scores, const int* is_valid, + int n, int nP) nogil: + cdef int best = -1 + cdef float best_score = 0 + for i in range(n): + if is_valid[i] >= 1: + for j in range(nP): + if best == -1 or scores[i*nP+j] > best_score: + best = i + best_score = scores[i*nP+j] + return best + + cdef int _arg_max_clas(const weight_t* scores, int move, const Transition* actions, int nr_class) except -1: cdef weight_t score = 0 @@ -531,3 +804,30 @@ cdef int _arg_max_clas(const weight_t* scores, int move, const Transition* actio mode = i score = scores[i] return mode + + +# These are passed as callbacks to thinc.search.Beam +cdef int _transition_state(void* _dest, void* _src, class_t clas, void* _moves) except -1: + dest = _dest + src = _src + moves = _moves + dest.clone(src) + moves[clas].do(dest.c, moves[clas].label) + + +cdef int _check_final_state(void* _state, void* extra_args) except -1: + return (_state).is_final() + + +def _cleanup(Beam beam): + for i in range(beam.width): + Py_XDECREF(beam._states[i].content) + Py_XDECREF(beam._parents[i].content) + + +cdef hash_t _hash_state(void* _state, void* _) except 0: + state = _state + if state.c.is_final(): + return 1 + else: + return state.c.hash() diff --git a/spacy/syntax/nonproj.pyx b/spacy/syntax/nonproj.pyx index 881d8d480..499effcda 100644 --- a/spacy/syntax/nonproj.pyx +++ b/spacy/syntax/nonproj.pyx @@ -1,10 +1,17 @@ # coding: utf-8 +""" +Implements the projectivize/deprojectivize mechanism in Nivre & Nilsson 2005 +for doing pseudo-projective parsing implementation uses the HEAD decoration +scheme. +""" from __future__ import unicode_literals from copy import copy from ..tokens.doc cimport Doc from ..attrs import DEP, HEAD +DELIMITER = '||' + def ancestors(tokenid, heads): # returns all words going from the word up the path to the root @@ -60,145 +67,124 @@ def is_nonproj_tree(heads): return any( is_nonproj_arc(word,heads) for word in range(len(heads)) ) -class PseudoProjectivity: - # implements the projectivize/deprojectivize mechanism in Nivre & Nilsson 2005 - # for doing pseudo-projective parsing - # implementation uses the HEAD decoration scheme - - delimiter = '||' - - @classmethod - def decompose(cls, label): - return label.partition(cls.delimiter)[::2] - - @classmethod - def is_decorated(cls, label): - return label.find(cls.delimiter) != -1 - - @classmethod - def preprocess_training_data(cls, gold_tuples, label_freq_cutoff=30): - preprocessed = [] - freqs = {} - for raw_text, sents in gold_tuples: - prepro_sents = [] - for (ids, words, tags, heads, labels, iob), ctnts in sents: - proj_heads,deco_labels = cls.projectivize(heads,labels) - # set the label to ROOT for each root dependent - deco_labels = [ 'ROOT' if head == i else deco_labels[i] for i,head in enumerate(proj_heads) ] - # count label frequencies - if label_freq_cutoff > 0: - for label in deco_labels: - if cls.is_decorated(label): - freqs[label] = freqs.get(label,0) + 1 - prepro_sents.append(((ids,words,tags,proj_heads,deco_labels,iob), ctnts)) - preprocessed.append((raw_text, prepro_sents)) - - if label_freq_cutoff > 0: - return cls._filter_labels(preprocessed,label_freq_cutoff,freqs) - return preprocessed +def decompose(label): + return label.partition(DELIMITER)[::2] - @classmethod - def projectivize(cls, heads, labels): - # use the algorithm by Nivre & Nilsson 2005 - # assumes heads to be a proper tree, i.e. connected and cycle-free - # returns a new pair (heads,labels) which encode - # a projective and decorated tree - proj_heads = copy(heads) - smallest_np_arc = cls._get_smallest_nonproj_arc(proj_heads) - if smallest_np_arc == None: # this sentence is already projective - return proj_heads, copy(labels) - while smallest_np_arc != None: - cls._lift(smallest_np_arc, proj_heads) - smallest_np_arc = cls._get_smallest_nonproj_arc(proj_heads) - deco_labels = cls._decorate(heads, proj_heads, labels) - return proj_heads, deco_labels +def is_decorated(label): + return label.find(DELIMITER) != -1 - @classmethod - def deprojectivize(cls, tokens): - # reattach arcs with decorated labels (following HEAD scheme) - # for each decorated arc X||Y, search top-down, left-to-right, - # breadth-first until hitting a Y then make this the new head - #parse = tokens.to_array([HEAD, DEP]) - for token in tokens: - if cls.is_decorated(token.dep_): - newlabel,headlabel = cls.decompose(token.dep_) - newhead = cls._find_new_head(token,headlabel) - token.head = newhead - token.dep_ = newlabel +def preprocess_training_data(gold_tuples, label_freq_cutoff=30): + preprocessed = [] + freqs = {} + for raw_text, sents in gold_tuples: + prepro_sents = [] + for (ids, words, tags, heads, labels, iob), ctnts in sents: + proj_heads,deco_labels = projectivize(heads,labels) + # set the label to ROOT for each root dependent + deco_labels = [ 'ROOT' if head == i else deco_labels[i] for i,head in enumerate(proj_heads) ] + # count label frequencies + if label_freq_cutoff > 0: + for label in deco_labels: + if is_decorated(label): + freqs[label] = freqs.get(label,0) + 1 + prepro_sents.append(((ids,words,tags,proj_heads,deco_labels,iob), ctnts)) + preprocessed.append((raw_text, prepro_sents)) - # tokens.attach(token,newhead,newlabel) - #parse[token.i,1] = tokens.vocab.strings[newlabel] - #parse[token.i,0] = newhead.i - token.i - #tokens.from_array([HEAD, DEP],parse) + if label_freq_cutoff > 0: + return _filter_labels(preprocessed,label_freq_cutoff,freqs) + return preprocessed - @classmethod - def _decorate(cls, heads, proj_heads, labels): - # uses decoration scheme HEAD from Nivre & Nilsson 2005 - assert(len(heads) == len(proj_heads) == len(labels)) - deco_labels = [] - for tokenid,head in enumerate(heads): - if head != proj_heads[tokenid]: - deco_labels.append('%s%s%s' % (labels[tokenid],cls.delimiter,labels[head])) - else: - deco_labels.append(labels[tokenid]) - return deco_labels +def projectivize(heads, labels): + # use the algorithm by Nivre & Nilsson 2005 + # assumes heads to be a proper tree, i.e. connected and cycle-free + # returns a new pair (heads,labels) which encode + # a projective and decorated tree + proj_heads = copy(heads) + smallest_np_arc = _get_smallest_nonproj_arc(proj_heads) + if smallest_np_arc == None: # this sentence is already projective + return proj_heads, copy(labels) + while smallest_np_arc != None: + _lift(smallest_np_arc, proj_heads) + smallest_np_arc = _get_smallest_nonproj_arc(proj_heads) + deco_labels = _decorate(heads, proj_heads, labels) + return proj_heads, deco_labels - @classmethod - def _get_smallest_nonproj_arc(cls, heads): - # return the smallest non-proj arc or None - # where size is defined as the distance between dep and head - # and ties are broken left to right - smallest_size = float('inf') - smallest_np_arc = None - for tokenid,head in enumerate(heads): - size = abs(tokenid-head) - if size < smallest_size and is_nonproj_arc(tokenid,heads): - smallest_size = size - smallest_np_arc = tokenid - return smallest_np_arc +def deprojectivize(tokens): + # reattach arcs with decorated labels (following HEAD scheme) + # for each decorated arc X||Y, search top-down, left-to-right, + # breadth-first until hitting a Y then make this the new head + for token in tokens: + if is_decorated(token.dep_): + newlabel,headlabel = decompose(token.dep_) + newhead = _find_new_head(token,headlabel) + token.head = newhead + token.dep_ = newlabel + return tokens + +def _decorate(heads, proj_heads, labels): + # uses decoration scheme HEAD from Nivre & Nilsson 2005 + assert(len(heads) == len(proj_heads) == len(labels)) + deco_labels = [] + for tokenid,head in enumerate(heads): + if head != proj_heads[tokenid]: + deco_labels.append('%s%s%s' % (labels[tokenid], DELIMITER, labels[head])) + else: + deco_labels.append(labels[tokenid]) + return deco_labels - @classmethod - def _lift(cls, tokenid, heads): - # reattaches a word to it's grandfather - head = heads[tokenid] - ghead = heads[head] - # attach to ghead if head isn't attached to root else attach to root - heads[tokenid] = ghead if head != ghead else tokenid +def _get_smallest_nonproj_arc(heads): + # return the smallest non-proj arc or None + # where size is defined as the distance between dep and head + # and ties are broken left to right + smallest_size = float('inf') + smallest_np_arc = None + for tokenid,head in enumerate(heads): + size = abs(tokenid-head) + if size < smallest_size and is_nonproj_arc(tokenid,heads): + smallest_size = size + smallest_np_arc = tokenid + return smallest_np_arc - @classmethod - def _find_new_head(cls, token, headlabel): - # search through the tree starting from the head of the given token - # returns the id of the first descendant with the given label - # if there is none, return the current head (no change) - queue = [token.head] - while queue: - next_queue = [] - for qtoken in queue: - for child in qtoken.children: - if child.is_space: continue - if child == token: continue - if child.dep_ == headlabel: - return child - next_queue.append(child) - queue = next_queue - return token.head +def _lift(tokenid, heads): + # reattaches a word to it's grandfather + head = heads[tokenid] + ghead = heads[head] + # attach to ghead if head isn't attached to root else attach to root + heads[tokenid] = ghead if head != ghead else tokenid - @classmethod - def _filter_labels(cls, gold_tuples, cutoff, freqs): - # throw away infrequent decorated labels - # can't learn them reliably anyway and keeps label set smaller - filtered = [] - for raw_text, sents in gold_tuples: - filtered_sents = [] - for (ids, words, tags, heads, labels, iob), ctnts in sents: - filtered_labels = [ cls.decompose(label)[0] if freqs.get(label,cutoff) < cutoff else label for label in labels ] - filtered_sents.append(((ids,words,tags,heads,filtered_labels,iob), ctnts)) - filtered.append((raw_text, filtered_sents)) - return filtered +def _find_new_head(token, headlabel): + # search through the tree starting from the head of the given token + # returns the id of the first descendant with the given label + # if there is none, return the current head (no change) + queue = [token.head] + while queue: + next_queue = [] + for qtoken in queue: + for child in qtoken.children: + if child.is_space: continue + if child == token: continue + if child.dep_ == headlabel: + return child + next_queue.append(child) + queue = next_queue + return token.head + + +def _filter_labels(gold_tuples, cutoff, freqs): + # throw away infrequent decorated labels + # can't learn them reliably anyway and keeps label set smaller + filtered = [] + for raw_text, sents in gold_tuples: + filtered_sents = [] + for (ids, words, tags, heads, labels, iob), ctnts in sents: + filtered_labels = [ decompose(label)[0] if freqs.get(label,cutoff) < cutoff else label for label in labels ] + filtered_sents.append(((ids,words,tags,heads,filtered_labels,iob), ctnts)) + filtered.append((raw_text, filtered_sents)) + return filtered diff --git a/spacy/syntax/parser.pyx b/spacy/syntax/parser.pyx index b9de1e114..78698db12 100644 --- a/spacy/syntax/parser.pyx +++ b/spacy/syntax/parser.pyx @@ -33,7 +33,6 @@ from ._parse_features cimport CONTEXT_SIZE from ._parse_features cimport fill_context from .stateclass cimport StateClass from ._state cimport StateC -from .nonproj import PseudoProjectivity from .transition_system import OracleError from .transition_system cimport TransitionSystem, Transition from ..structs cimport TokenC diff --git a/spacy/syntax/stateclass.pxd b/spacy/syntax/stateclass.pxd index 62fda5ade..0ae83ee27 100644 --- a/spacy/syntax/stateclass.pxd +++ b/spacy/syntax/stateclass.pxd @@ -4,6 +4,7 @@ from cymem.cymem cimport Pool cimport cython from ..structs cimport TokenC, Entity +from ..typedefs cimport attr_t from ..vocab cimport EMPTY_LEXEME from ._state cimport StateC @@ -105,19 +106,19 @@ cdef class StateClass: cdef inline void unshift(self) nogil: self.c.unshift() - cdef inline void add_arc(self, int head, int child, int label) nogil: + cdef inline void add_arc(self, int head, int child, attr_t label) nogil: self.c.add_arc(head, child, label) cdef inline void del_arc(self, int head, int child) nogil: self.c.del_arc(head, child) - cdef inline void open_ent(self, int label) nogil: + cdef inline void open_ent(self, attr_t label) nogil: self.c.open_ent(label) cdef inline void close_ent(self) nogil: self.c.close_ent() - cdef inline void set_ent_tag(self, int i, int ent_iob, int ent_type) nogil: + cdef inline void set_ent_tag(self, int i, int ent_iob, attr_t ent_type) nogil: self.c.set_ent_tag(i, ent_iob, ent_type) cdef inline void set_break(self, int i) nogil: diff --git a/spacy/syntax/stateclass.pyx b/spacy/syntax/stateclass.pyx index fd38710e7..228a3ff91 100644 --- a/spacy/syntax/stateclass.pyx +++ b/spacy/syntax/stateclass.pyx @@ -41,6 +41,11 @@ cdef class StateClass: def is_final(self): return self.c.is_final() + def copy(self): + cdef StateClass new_state = StateClass.init(self.c._sent, self.c.length) + new_state.c.clone(self.c) + return new_state + def print_state(self, words): words = list(words) + ['_'] top = words[self.S(0)] + '_%d' % self.S_(0).head diff --git a/spacy/syntax/transition_system.pxd b/spacy/syntax/transition_system.pxd index 5169ff7ca..bea58e9c3 100644 --- a/spacy/syntax/transition_system.pxd +++ b/spacy/syntax/transition_system.pxd @@ -1,6 +1,7 @@ from cymem.cymem cimport Pool from thinc.typedefs cimport weight_t +from ..typedefs cimport attr_t from ..structs cimport TokenC from ..gold cimport GoldParse from ..gold cimport GoldParseC @@ -13,20 +14,22 @@ from ._state cimport StateC cdef struct Transition: int clas int move - int label + attr_t label weight_t score - bint (*is_valid)(const StateC* state, int label) nogil - weight_t (*get_cost)(StateClass state, const GoldParseC* gold, int label) nogil - int (*do)(StateC* state, int label) nogil + bint (*is_valid)(const StateC* state, attr_t label) nogil + weight_t (*get_cost)(StateClass state, const GoldParseC* gold, attr_t label) nogil + int (*do)(StateC* state, attr_t label) nogil -ctypedef weight_t (*get_cost_func_t)(StateClass state, const GoldParseC* gold, int label) nogil +ctypedef weight_t (*get_cost_func_t)(StateClass state, const GoldParseC* gold, + attr_tlabel) nogil ctypedef weight_t (*move_cost_func_t)(StateClass state, const GoldParseC* gold) nogil -ctypedef weight_t (*label_cost_func_t)(StateClass state, const GoldParseC* gold, int label) nogil +ctypedef weight_t (*label_cost_func_t)(StateClass state, const GoldParseC* + gold, attr_t label) nogil -ctypedef int (*do_func_t)(StateC* state, int label) nogil +ctypedef int (*do_func_t)(StateC* state, attr_t label) nogil ctypedef void* (*init_state_t)(Pool mem, int length, void* tokens) except NULL @@ -36,18 +39,16 @@ cdef class TransitionSystem: cdef Transition* c cdef readonly int n_moves cdef int _size - cdef public int root_label + cdef public attr_t root_label cdef public freqs cdef init_state_t init_beam_state cdef int initialize_state(self, StateC* state) nogil cdef int finalize_state(self, StateC* state) nogil - cdef int preprocess_gold(self, GoldParse gold) except -1 - cdef Transition lookup_transition(self, object name) except * - cdef Transition init_transition(self, int clas, int move, int label) except * + cdef Transition init_transition(self, int clas, int move, attr_t label) except * cdef int set_valid(self, int* output, const StateC* st) nogil diff --git a/spacy/syntax/transition_system.pyx b/spacy/syntax/transition_system.pyx index 45ff5b5c9..d3f64f827 100644 --- a/spacy/syntax/transition_system.pyx +++ b/spacy/syntax/transition_system.pyx @@ -5,11 +5,14 @@ from __future__ import unicode_literals from cpython.ref cimport PyObject, Py_INCREF, Py_XDECREF from cymem.cymem cimport Pool from thinc.typedefs cimport weight_t -from collections import defaultdict +from collections import defaultdict, OrderedDict +import ujson +from .. import util from ..structs cimport TokenC from .stateclass cimport StateClass from ..attrs cimport TAG, HEAD, DEP, ENT_TYPE, ENT_IOB +from ..typedefs cimport attr_t cdef weight_t MIN_SCORE = -90000 @@ -26,7 +29,7 @@ cdef void* _init_state(Pool mem, int length, void* tokens) except NULL: cdef class TransitionSystem: - def __init__(self, StringStore string_table, dict labels_by_action, _freqs=None): + def __init__(self, StringStore string_table, labels_by_action): self.mem = Pool() self.strings = string_table self.n_moves = 0 @@ -34,28 +37,20 @@ cdef class TransitionSystem: self.c = self.mem.alloc(self._size, sizeof(Transition)) - for action, label_strs in sorted(labels_by_action.items()): + for action, label_strs in labels_by_action.items(): for label_str in label_strs: self.add_action(int(action), label_str) - self.root_label = self.strings['ROOT'] - self.freqs = {} if _freqs is None else _freqs - for attr in (TAG, HEAD, DEP, ENT_TYPE, ENT_IOB): - self.freqs[attr] = defaultdict(int) - self.freqs[attr][0] = 1 - # Ensure we've seen heads. Need an official dependency length limit... - for i in range(10024): - self.freqs[HEAD][i] = 1 - self.freqs[HEAD][-i] = 1 + self.root_label = self.strings.add('ROOT') self.init_beam_state = _init_state def __reduce__(self): - labels_by_action = {} + labels_by_action = OrderedDict() cdef Transition t for trans in self.c[:self.n_moves]: label_str = self.strings[trans.label] labels_by_action.setdefault(trans.move, []).append(label_str) return (self.__class__, - (self.strings, labels_by_action, self.freqs), + (self.strings, labels_by_action), None, None) def init_batch(self, docs): @@ -69,6 +64,29 @@ cdef class TransitionSystem: offset += len(doc) return states + def get_oracle_sequence(self, doc, GoldParse gold): + cdef Pool mem = Pool() + costs = mem.alloc(self.n_moves, sizeof(float)) + is_valid = mem.alloc(self.n_moves, sizeof(int)) + + cdef StateClass state = StateClass(doc, offset=0) + self.initialize_state(state.c) + history = [] + while not state.is_final(): + self.set_costs(is_valid, costs, state, gold) + for i in range(self.n_moves): + if is_valid[i] and costs[i] <= 0: + action = self.c[i] + history.append(i) + action.do(state.c, action.label) + break + else: + print(gold.words) + print(gold.ner) + print(history) + raise ValueError("Could not find gold move") + return history + cdef int initialize_state(self, StateC* state) nogil: pass @@ -78,17 +96,19 @@ cdef class TransitionSystem: def finalize_doc(self, doc): pass - cdef int preprocess_gold(self, GoldParse gold) except -1: + def preprocess_gold(self, GoldParse gold): raise NotImplementedError cdef Transition lookup_transition(self, object name) except *: raise NotImplementedError - cdef Transition init_transition(self, int clas, int move, int label) except *: + cdef Transition init_transition(self, int clas, int move, attr_t label) except *: raise NotImplementedError def is_valid(self, StateClass stcls, move_name): action = self.lookup_transition(move_name) + if action.move == 0: + return False return action.is_valid(stcls.c, action.label) cdef int set_valid(self, int* is_valid, const StateC* st) nogil: @@ -100,24 +120,80 @@ cdef class TransitionSystem: StateClass stcls, GoldParse gold) except -1: cdef int i self.set_valid(is_valid, stcls.c) + cdef int n_gold = 0 for i in range(self.n_moves): if is_valid[i]: costs[i] = self.c[i].get_cost(stcls, &gold.c, self.c[i].label) + n_gold += costs[i] <= 0 else: costs[i] = 9000 + if n_gold <= 0: + print(gold.words) + print(gold.ner) + print([gold.c.ner[i].clas for i in range(gold.length)]) + print([gold.c.ner[i].move for i in range(gold.length)]) + print([gold.c.ner[i].label for i in range(gold.length)]) + print("Self labels", [self.c[i].label for i in range(self.n_moves)]) + raise ValueError( + "Could not find a gold-standard action to supervise " + "the entity recognizer\n" + "The transition system has %d actions." % (self.n_moves)) - def add_action(self, int action, label): - if not isinstance(label, int): - label = self.strings[label] + def get_class_name(self, int clas): + act = self.c[clas] + return self.move_name(act.move, act.label) + + def add_action(self, int action, label_name): + cdef attr_t label_id + if not isinstance(label_name, int): + label_id = self.strings.add(label_name) + else: + label_id = label_name # Check we're not creating a move we already have, so that this is # idempotent for trans in self.c[:self.n_moves]: - if trans.move == action and trans.label == label: + if trans.move == action and trans.label == label_id: return 0 if self.n_moves >= self._size: self._size *= 2 self.c = self.mem.realloc(self.c, self._size * sizeof(self.c[0])) - - self.c[self.n_moves] = self.init_transition(self.n_moves, action, label) + self.c[self.n_moves] = self.init_transition(self.n_moves, action, label_id) + assert self.c[self.n_moves].label == label_id self.n_moves += 1 return 1 + + def to_disk(self, path, **exclude): + with path.open('wb') as file_: + file_.write(self.to_bytes(**exclude)) + + def from_disk(self, path, **exclude): + with path.open('rb') as file_: + byte_data = file_.read() + self.from_bytes(byte_data, **exclude) + return self + + def to_bytes(self, **exclude): + transitions = [] + for trans in self.c[:self.n_moves]: + transitions.append({ + 'clas': trans.clas, + 'move': trans.move, + 'label': self.strings[trans.label], + 'name': self.move_name(trans.move, trans.label) + }) + serializers = { + 'transitions': lambda: ujson.dumps(transitions), + 'strings': lambda: self.strings.to_bytes() + } + return util.to_bytes(serializers, exclude) + + def from_bytes(self, bytes_data, **exclude): + transitions = [] + deserializers = { + 'transitions': lambda b: transitions.extend(ujson.loads(b)), + 'strings': lambda b: self.strings.from_bytes(b) + } + msg = util.from_bytes(bytes_data, deserializers, exclude) + for trans in transitions: + self.add_action(trans['move'], trans['label']) + return self diff --git a/spacy/tagger.pyx b/spacy/tagger.pyx index 59e8a2c66..0fadea15d 100644 --- a/spacy/tagger.pyx +++ b/spacy/tagger.pyx @@ -1,7 +1,6 @@ # coding: utf8 from __future__ import unicode_literals -import ujson from collections import defaultdict from cymem.cymem cimport Pool @@ -15,7 +14,6 @@ from .tokens.doc cimport Doc from .attrs cimport TAG from .gold cimport GoldParse from .attrs cimport * -from . import util cpdef enum: @@ -108,55 +106,15 @@ cdef inline void _fill_from_token(atom_t* context, const TokenC* t) nogil: cdef class Tagger: - """ - Annotate part-of-speech tags on Doc objects. - """ - @classmethod - def load(cls, path, vocab, require=False): - """ - Load the statistical model from the supplied path. - - Arguments: - path (Path): - The path to load from. - vocab (Vocab): - The vocabulary. Must be shared by the documents to be processed. - require (bool): - Whether to raise an error if the files are not found. - Returns (Tagger): - The newly created object. - """ - # TODO: Change this to expect config.json when we don't have to - # support old data. - path = util.ensure_path(path) - if (path / 'templates.json').exists(): - with (path / 'templates.json').open('r', encoding='utf8') as file_: - templates = ujson.load(file_) - elif require: - raise IOError( - "Required file %s/templates.json not found when loading Tagger" % str(path)) - else: - templates = cls.feature_templates - self = cls(vocab, model=None, feature_templates=templates) - - if (path / 'model').exists(): - self.model.load(str(path / 'model')) - elif require: - raise IOError( - "Required file %s/model not found when loading Tagger" % str(path)) - return self + """Annotate part-of-speech tags on Doc objects.""" def __init__(self, Vocab vocab, TaggerModel model=None, **cfg): - """ - Create a Tagger. + """Create a Tagger. - Arguments: - vocab (Vocab): - The vocabulary object. Must be shared with documents to be processed. - model (thinc.linear.AveragedPerceptron): - The statistical model. - Returns (Tagger): - The newly constructed object. + vocab (Vocab): The vocabulary object. Must be shared with documents to + be processed. + model (thinc.linear.AveragedPerceptron): The statistical model. + RETURNS (Tagger): The newly constructed object. """ if model is None: model = TaggerModel(cfg.get('features', self.feature_templates), @@ -186,13 +144,9 @@ cdef class Tagger: tokens._py_tokens = [None] * tokens.length def __call__(self, Doc tokens): - """ - Apply the tagger, setting the POS tags onto the Doc object. + """Apply the tagger, setting the POS tags onto the Doc object. - Arguments: - doc (Doc): The tokens to be tagged. - Returns: - None + doc (Doc): The tokens to be tagged. """ if tokens.length == 0: return 0 @@ -215,34 +169,25 @@ cdef class Tagger: tokens._py_tokens = [None] * tokens.length def pipe(self, stream, batch_size=1000, n_threads=2): - """ - Tag a stream of documents. + """Tag a stream of documents. Arguments: - stream: The sequence of documents to tag. - batch_size (int): - The number of documents to accumulate into a working set. - n_threads (int): - The number of threads with which to work on the buffer in parallel, - if the Matcher implementation supports multi-threading. - Yields: - Doc Documents, in order. + stream: The sequence of documents to tag. + batch_size (int): The number of documents to accumulate into a working set. + n_threads (int): The number of threads with which to work on the buffer + in parallel, if the Matcher implementation supports multi-threading. + YIELDS (Doc): Documents, in order. """ for doc in stream: self(doc) yield doc def update(self, Doc tokens, GoldParse gold, itn=0): - """ - Update the statistical model, with tags supplied for the given document. + """Update the statistical model, with tags supplied for the given document. - Arguments: - doc (Doc): - The document to update on. - gold (GoldParse): - Manager for the gold-standard tags. - Returns (int): - Number of tags correct. + doc (Doc): The document to update on. + gold (GoldParse): Manager for the gold-standard tags. + RETURNS (int): Number of tags predicted correctly. """ gold_tag_strs = gold.tags assert len(tokens) == len(gold_tag_strs) diff --git a/spacy/tests/README.md b/spacy/tests/README.md index a7699ed54..fd47ae579 100644 --- a/spacy/tests/README.md +++ b/spacy/tests/README.md @@ -13,21 +13,32 @@ Tests for spaCy modules and classes live in their own directories of the same na 2. [Dos and don'ts](#dos-and-donts) 3. [Parameters](#parameters) 4. [Fixtures](#fixtures) -5. [Helpers and utilities](#helpers-and-utilities) -6. [Contributing to the tests](#contributing-to-the-tests) +5. [Testing models](#testing-models) +6. [Helpers and utilities](#helpers-and-utilities) +7. [Contributing to the tests](#contributing-to-the-tests) ## Running the tests +To show print statements, run the tests with `py.test -s`. To abort after the +first failure, run them with `py.test -x`. + ```bash -py.test spacy # run basic tests -py.test spacy --models # run basic and model tests -py.test spacy --slow # run basic and slow tests -py.test spacy --models --slow # run all tests +py.test spacy # run basic tests +py.test spacy --models --en # run basic and English model tests +py.test spacy --models --all # run basic and all model tests +py.test spacy --slow # run basic and slow tests +py.test spacy --models --all --slow # run all tests ``` -To show print statements, run the tests with `py.test -s`. To abort after the first failure, run them with `py.test -x`. +You can also run tests in a specific file or directory, or even only one +specific test: +```bash +py.test spacy/tests/tokenizer # run all tests in directory +py.test spacy/tests/tokenizer/test_exceptions.py # run all tests in file +py.test spacy/tests/tokenizer/test_exceptions.py::test_tokenizer_handles_emoji # run specific test +``` ## Dos and don'ts @@ -83,14 +94,9 @@ These are the main fixtures that are currently available: | Fixture | Description | | --- | --- | | `tokenizer` | Creates **all available** language tokenizers and runs the test for **each of them**. | -| `en_tokenizer` | Creates an English `Tokenizer` object. | -| `de_tokenizer` | Creates a German `Tokenizer` object. | -| `hu_tokenizer` | Creates a Hungarian `Tokenizer` object. | -| `en_vocab` | Creates an English `Vocab` object. | -| `en_entityrecognizer` | Creates an English `EntityRecognizer` object. | -| `lemmatizer` | Creates a `Lemmatizer` object from the installed language data (`None` if no data is found). -| `EN` | Creates an instance of `English`. Only use for tests that require the models. | -| `DE` | Creates an instance of `German`. Only use for tests that require the models. | +| `en_tokenizer`, `de_tokenizer`, ... | Creates an English, German etc. tokenizer. | +| `en_vocab`, `en_entityrecognizer`, ... | Creates an instance of the English `Vocab`, `EntityRecognizer` object etc. | +| `EN`, `DE`, ... | Creates a language class with a loaded model. For more info, see [Testing models](#testing-models). | | `text_file` | Creates an instance of `StringIO` to simulate reading from and writing to files. | | `text_file_b` | Creates an instance of `ByteIO` to simulate reading from and writing to files. | @@ -103,6 +109,48 @@ def test_module_do_something(en_tokenizer): If all tests in a file require a specific configuration, or use the same complex example, it can be helpful to create a separate fixture. This fixture should be added at the top of each file. Make sure to use descriptive names for these fixtures and don't override any of the global fixtures listed above. **From looking at a test, it should immediately be clear which fixtures are used, and where they are coming from.** +## Testing models + +Models should only be loaded and tested **if absolutely necessary** – for example, if you're specifically testing a model's performance, or if your test is related to model loading. If you only need an annotated `Doc`, you should use the `get_doc()` helper function to create it manually instead. + +To specify which language models a test is related to, set the language ID as an argument of `@pytest.mark.models`. This allows you to later run the tests with `--models --en`. You can then use the `EN` [fixture](#fixtures) to get a language +class with a loaded model. + +```python +@pytest.mark.models('en') +def test_english_model(EN): + doc = EN(u'This is a test') +``` + +> ⚠️ **Important note:** In order to test models, they need to be installed as a packge. The [conftest.py](conftest.py) includes a list of all available models, mapped to their IDs, e.g. `en`. Unless otherwise specified, each model that's installed in your environment will be imported and tested. If you don't have a model installed, **the test will be skipped**. + +Under the hood, `pytest.importorskip` is used to import a model package and skip the test if the package is not installed. The `EN` fixture for example gets all +available models for `en`, [parametrizes](#parameters) them to run the test for *each of them*, and uses `load_test_model()` to import the model and run the test, or skip it if the model is not installed. + +### Testing specific models + +Using the `load_test_model()` helper function, you can also write tests for specific models, or combinations of them: + +```python +from .util import load_test_model + +@pytest.mark.models('en') +def test_en_md_only(): + nlp = load_test_model('en_core_web_md') + # test something specific to en_core_web_md + +@pytest.mark.models('en', 'fr') +@pytest.mark.parametrize('model', ['en_core_web_md', 'fr_depvec_web_lg']) +def test_different_models(model): + nlp = load_test_model(model) + # test something specific to the parametrized models +``` + +### Known issues and future improvements + +Using `importorskip` on a list of model packages is not ideal and we're looking to improve this in the future. But at the moment, it's the best way to ensure that tests are performed on specific model packages only, and that you'll always be able to run the tests, even if you don't have *all available models* installed. (If the tests made a call to `spacy.load('en')` instead, this would load whichever model you've created an `en` shortcut for. This may be one of spaCy's default models, but it could just as easily be your own custom English model.) + +The current setup also doesn't provide an easy way to only run tests on specific model versions. The `minversion` keyword argument on `pytest.importorskip` can take care of this, but it currently only checks for the package's `__version__` attribute. An alternative solution would be to load a model package's meta.json and skip if the model's version does not match the one specified in the test. ## Helpers and utilities @@ -152,11 +200,11 @@ print([token.dep_ for token in doc]) **Note:** There's currently no way of setting the serializer data for the parser without loading the models. If this is relevant to your test, constructing the `Doc` via `get_doc()` won't work. - ### Other utilities | Name | Description | | --- | --- | +| `load_test_model` | Load a model if it's installed as a package, otherwise skip test. | | `apply_transition_sequence(parser, doc, sequence)` | Perform a series of pre-specified transitions, to put the parser in a desired state. | | `add_vecs_to_vocab(vocab, vectors)` | Add list of vector tuples (`[("text", [1, 2, 3])]`) to given vocab. All vectors need to have the same length. | | `get_cosine(vec1, vec2)` | Get cosine for two given vectors. | diff --git a/spacy/tests/conftest.py b/spacy/tests/conftest.py index 6b577be62..200f9ff4f 100644 --- a/spacy/tests/conftest.py +++ b/spacy/tests/conftest.py @@ -1,25 +1,50 @@ # coding: utf-8 from __future__ import unicode_literals -from ..tokens import Doc -from ..strings import StringStore -from ..lemmatizer import Lemmatizer -from ..attrs import ORTH, TAG, HEAD, DEP -from .. import util - from io import StringIO, BytesIO from pathlib import Path import pytest +from .util import load_test_model +from ..tokens import Doc +from ..strings import StringStore +from .. import util + _languages = ['bn', 'da', 'de', 'en', 'es', 'fi', 'fr', 'he', 'hu', 'it', 'nb', - 'nl', 'pl', 'pt', 'sv'] + 'nl', 'pl', 'pt', 'sv', 'xx'] +_models = {'en': ['en_depent_web_sm', 'en_core_web_md'], + 'de': ['de_core_news_md'], + 'fr': ['fr_depvec_web_lg'], + 'xx': ['xx_ent_web_md']} -@pytest.fixture(params=_languages) -def tokenizer(request): - lang = util.get_lang_class(request.param) - return lang.Defaults.create_tokenizer() +# only used for tests that require loading the models +# in all other cases, use specific instances + +@pytest.fixture(params=_models['en']) +def EN(request): + return load_test_model(request.param) + + +@pytest.fixture(params=_models['de']) +def DE(request): + return load_test_model(request.param) + + +@pytest.fixture(params=_models['fr']) +def FR(request): + return load_test_model(request.param) + + +#@pytest.fixture(params=_languages) +#def tokenizer(request): + #lang = util.get_lang_class(request.param) + #return lang.Defaults.create_tokenizer() + +@pytest.fixture +def tokenizer(): + return util.get_lang_class('xx').Defaults.create_tokenizer() @pytest.fixture @@ -47,7 +72,7 @@ def de_tokenizer(): return util.get_lang_class('de').Defaults.create_tokenizer() -@pytest.fixture(scope='module') +@pytest.fixture def fr_tokenizer(): return util.get_lang_class('fr').Defaults.create_tokenizer() @@ -91,11 +116,6 @@ def en_entityrecognizer(): return util.get_lang_class('en').Defaults.create_entity() -@pytest.fixture -def lemmatizer(): - return util.get_lang_class('en').Defaults.create_lemmatizer() - - @pytest.fixture def text_file(): return StringIO() @@ -105,22 +125,6 @@ def text_file_b(): return BytesIO() -# only used for tests that require loading the models -# in all other cases, use specific instances -@pytest.fixture(scope="session") -def EN(): - return English() - - -@pytest.fixture(scope="session") -def DE(): - return German() - -@pytest.fixture(scope="session") -def FR(): - return French() - - def pytest_addoption(parser): parser.addoption("--models", action="store_true", help="include tests that require full models") @@ -129,8 +133,18 @@ def pytest_addoption(parser): parser.addoption("--slow", action="store_true", help="include slow tests") + for lang in _languages + ['all']: + parser.addoption("--%s" % lang, action="store_true", help="Use %s models" % lang) + def pytest_runtest_setup(item): for opt in ['models', 'vectors', 'slow']: if opt in item.keywords and not item.config.getoption("--%s" % opt): pytest.skip("need --%s option to run" % opt) + + # Check if test is marked with models and has arguments set, i.e. specific + # language. If so, skip test if flag not set. + if item.get_marker('models'): + for arg in item.get_marker('models').args: + if not item.config.getoption("--%s" % arg) and not item.config.getoption("--all"): + pytest.skip("need --%s or --all option to run" % arg) diff --git a/spacy/tests/doc/test_doc_api.py b/spacy/tests/doc/test_doc_api.py index 1bc534ecd..cbe1bbc66 100644 --- a/spacy/tests/doc/test_doc_api.py +++ b/spacy/tests/doc/test_doc_api.py @@ -102,7 +102,7 @@ def test_doc_api_getitem(en_tokenizer): def test_doc_api_serialize(en_tokenizer, text): tokens = en_tokenizer(text) new_tokens = get_doc(tokens.vocab).from_bytes(tokens.to_bytes()) - assert tokens.string == new_tokens.string + assert tokens.text == new_tokens.text assert [t.text for t in tokens] == [t.text for t in new_tokens] assert [t.orth for t in tokens] == [t.orth for t in new_tokens] @@ -204,6 +204,7 @@ def test_doc_api_right_edge(en_tokenizer): assert doc[6].right_edge.text == ',' +@pytest.mark.xfail @pytest.mark.parametrize('text,vectors', [ ("apple orange pear", ["apple -1 -1 -1", "orange -1 -1 0", "pear -1 0 -1"]) ]) diff --git a/spacy/tests/doc/test_token_api.py b/spacy/tests/doc/test_token_api.py index 959ff017b..00caa1445 100644 --- a/spacy/tests/doc/test_token_api.py +++ b/spacy/tests/doc/test_token_api.py @@ -68,6 +68,7 @@ def test_doc_token_api_is_properties(en_vocab): assert doc[5].like_email +@pytest.mark.xfail @pytest.mark.parametrize('text,vectors', [ ("apples oranges ldskbjls", ["apples -1 -1 -1", "oranges -1 -1 0"]) ]) @@ -99,8 +100,8 @@ def test_doc_token_api_ancestors(en_tokenizer): assert [t.text for t in doc[1].ancestors] == ["saw"] assert [t.text for t in doc[2].ancestors] == [] - assert doc[2].is_ancestor_of(doc[7]) - assert not doc[6].is_ancestor_of(doc[2]) + assert doc[2].is_ancestor(doc[7]) + assert not doc[6].is_ancestor(doc[2]) def test_doc_token_api_head_setter(en_tokenizer): @@ -155,3 +156,15 @@ def test_doc_token_api_head_setter(en_tokenizer): assert doc[3].left_edge.i == 0 assert doc[4].left_edge.i == 0 assert doc[2].left_edge.i == 0 + + +def test_sent_start(en_tokenizer): + doc = en_tokenizer(u'This is a sentence. This is another.') + assert not doc[0].sent_start + assert not doc[5].sent_start + doc[5].sent_start = True + assert doc[5].sent_start + assert not doc[0].sent_start + doc.is_parsed = True + assert len(list(doc.sents)) == 2 + diff --git a/spacy/tests/integration/__init__.py b/spacy/tests/integration/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/spacy/tests/integration/test_model_sanity.py b/spacy/tests/integration/test_model_sanity.py deleted file mode 100644 index ec231baaf..000000000 --- a/spacy/tests/integration/test_model_sanity.py +++ /dev/null @@ -1,72 +0,0 @@ -# coding: utf-8 - -import pytest -import numpy - - -@pytest.mark.models -class TestModelSanity: - """ - This is to make sure the model works as expected. The tests make sure that - values are properly set. - Tests are not meant to evaluate the content of the output, only make sure - the output is formally okay. - """ - @pytest.fixture(scope='class', params=['en','de']) - def example(self, request, EN, DE): - assert EN.entity != None - assert DE.entity != None - if request.param == 'en': - doc = EN(u'There was a stranger standing at the big ' + - u'street talking to herself.') - elif request.param == 'de': - doc = DE(u'An der großen Straße stand eine merkwürdige ' + - u'Gestalt und führte Selbstgespräche.') - return doc - - def test_tokenization(self, example): - # tokenization should split the document into tokens - assert len(example) > 1 - - def test_tagging(self, example): - # if tagging was done properly, pos tags shouldn't be empty - assert example.is_tagged - assert all( t.pos != 0 for t in example ) - assert all( t.tag != 0 for t in example ) - - def test_parsing(self, example): - # if parsing was done properly - # - dependency labels shouldn't be empty - # - the head of some tokens should not be root - assert example.is_parsed - assert all( t.dep != 0 for t in example ) - assert any( t.dep != i for i,t in enumerate(example) ) - - def test_ner(self, example): - # if ner was done properly, ent_iob shouldn't be empty - assert all([t.ent_iob != 0 for t in example]) - - def test_vectors(self, example): - # if vectors are available, they should differ on different words - # this isn't a perfect test since this could in principle fail - # in a sane model as well, - # but that's very unlikely and a good indicator if something is wrong - vector0 = example[0].vector - vector1 = example[1].vector - vector2 = example[2].vector - assert not numpy.array_equal(vector0,vector1) - assert not numpy.array_equal(vector0,vector2) - assert not numpy.array_equal(vector1,vector2) - - def test_probs(self, example): - # if frequencies/probabilities are okay, they should differ for - # different words - # this isn't a perfect test since this could in principle fail - # in a sane model as well, - # but that's very unlikely and a good indicator if something is wrong - prob0 = example[0].prob - prob1 = example[1].prob - prob2 = example[2].prob - assert not prob0 == prob1 - assert not prob0 == prob2 - assert not prob1 == prob2 diff --git a/spacy/tests/lang/de/test_exceptions.py b/spacy/tests/lang/de/test_exceptions.py index 13da3dc33..f7db648c9 100644 --- a/spacy/tests/lang/de/test_exceptions.py +++ b/spacy/tests/lang/de/test_exceptions.py @@ -8,20 +8,33 @@ import pytest @pytest.mark.parametrize('text', ["auf'm", "du's", "über'm", "wir's"]) -def test_tokenizer_splits_contractions(de_tokenizer, text): +def test_de_tokenizer_splits_contractions(de_tokenizer, text): tokens = de_tokenizer(text) assert len(tokens) == 2 @pytest.mark.parametrize('text', ["z.B.", "d.h.", "Jan.", "Dez.", "Chr."]) -def test_tokenizer_handles_abbr(de_tokenizer, text): +def test_de_tokenizer_handles_abbr(de_tokenizer, text): tokens = de_tokenizer(text) assert len(tokens) == 1 -def test_tokenizer_handles_exc_in_text(de_tokenizer): +def test_de_tokenizer_handles_exc_in_text(de_tokenizer): text = "Ich bin z.Zt. im Urlaub." tokens = de_tokenizer(text) assert len(tokens) == 6 assert tokens[2].text == "z.Zt." assert tokens[2].lemma_ == "zur Zeit" + + +@pytest.mark.parametrize('text,norms', [("vor'm", ["vor", "dem"]), ("du's", ["du", "es"])]) +def test_de_tokenizer_norm_exceptions(de_tokenizer, text, norms): + tokens = de_tokenizer(text) + assert [token.norm_ for token in tokens] == norms + + +@pytest.mark.xfail +@pytest.mark.parametrize('text,norm', [("daß", "dass")]) +def test_de_lex_attrs_norm_exceptions(de_tokenizer, text, norm): + tokens = de_tokenizer(text) + assert tokens[0].norm_ == norm diff --git a/spacy/tests/lang/de/test_models.py b/spacy/tests/lang/de/test_models.py new file mode 100644 index 000000000..85a04a183 --- /dev/null +++ b/spacy/tests/lang/de/test_models.py @@ -0,0 +1,77 @@ +# coding: utf-8 +from __future__ import unicode_literals + +import numpy +import pytest + + +@pytest.fixture +def example(DE): + """ + This is to make sure the model works as expected. The tests make sure that + values are properly set. Tests are not meant to evaluate the content of the + output, only make sure the output is formally okay. + """ + assert DE.entity != None + return DE('An der großen Straße stand eine merkwürdige Gestalt und führte Selbstgespräche.') + + +@pytest.mark.models('de') +def test_de_models_tokenization(example): + # tokenization should split the document into tokens + assert len(example) > 1 + + +@pytest.mark.xfail +@pytest.mark.models('de') +def test_de_models_tagging(example): + # if tagging was done properly, pos tags shouldn't be empty + assert example.is_tagged + assert all(t.pos != 0 for t in example) + assert all(t.tag != 0 for t in example) + + +@pytest.mark.models('de') +def test_de_models_parsing(example): + # if parsing was done properly + # - dependency labels shouldn't be empty + # - the head of some tokens should not be root + assert example.is_parsed + assert all(t.dep != 0 for t in example) + assert any(t.dep != i for i,t in enumerate(example)) + + +@pytest.mark.models('de') +def test_de_models_ner(example): + # if ner was done properly, ent_iob shouldn't be empty + assert all([t.ent_iob != 0 for t in example]) + + +@pytest.mark.models('de') +def test_de_models_vectors(example): + # if vectors are available, they should differ on different words + # this isn't a perfect test since this could in principle fail + # in a sane model as well, + # but that's very unlikely and a good indicator if something is wrong + vector0 = example[0].vector + vector1 = example[1].vector + vector2 = example[2].vector + assert not numpy.array_equal(vector0,vector1) + assert not numpy.array_equal(vector0,vector2) + assert not numpy.array_equal(vector1,vector2) + + +@pytest.mark.xfail +@pytest.mark.models('de') +def test_de_models_probs(example): + # if frequencies/probabilities are okay, they should differ for + # different words + # this isn't a perfect test since this could in principle fail + # in a sane model as well, + # but that's very unlikely and a good indicator if something is wrong + prob0 = example[0].prob + prob1 = example[1].prob + prob2 = example[2].prob + assert not prob0 == prob1 + assert not prob0 == prob2 + assert not prob1 == prob2 diff --git a/spacy/tests/lang/de/test_parser.py b/spacy/tests/lang/de/test_parser.py new file mode 100644 index 000000000..6b5b25901 --- /dev/null +++ b/spacy/tests/lang/de/test_parser.py @@ -0,0 +1,35 @@ +# coding: utf-8 +from __future__ import unicode_literals + +from ...util import get_doc + +import pytest + + +def test_de_parser_noun_chunks_standard_de(de_tokenizer): + text = "Eine Tasse steht auf dem Tisch." + heads = [1, 1, 0, -1, 1, -2, -4] + tags = ['ART', 'NN', 'VVFIN', 'APPR', 'ART', 'NN', '$.'] + deps = ['nk', 'sb', 'ROOT', 'mo', 'nk', 'nk', 'punct'] + + tokens = de_tokenizer(text) + doc = get_doc(tokens.vocab, [t.text for t in tokens], tags=tags, deps=deps, heads=heads) + chunks = list(doc.noun_chunks) + assert len(chunks) == 2 + assert chunks[0].text_with_ws == "Eine Tasse " + assert chunks[1].text_with_ws == "dem Tisch " + + +def test_de_extended_chunk(de_tokenizer): + text = "Die Sängerin singt mit einer Tasse Kaffee Arien." + heads = [1, 1, 0, -1, 1, -2, -1, -5, -6] + tags = ['ART', 'NN', 'VVFIN', 'APPR', 'ART', 'NN', 'NN', 'NN', '$.'] + deps = ['nk', 'sb', 'ROOT', 'mo', 'nk', 'nk', 'nk', 'oa', 'punct'] + + tokens = de_tokenizer(text) + doc = get_doc(tokens.vocab, [t.text for t in tokens], tags=tags, deps=deps, heads=heads) + chunks = list(doc.noun_chunks) + assert len(chunks) == 3 + assert chunks[0].text_with_ws == "Die Sängerin " + assert chunks[1].text_with_ws == "einer Tasse Kaffee " + assert chunks[2].text_with_ws == "Arien " diff --git a/spacy/tests/lang/en/test_contractions.py b/spacy/tests/lang/en/test_contractions.py deleted file mode 100644 index a97b8f5ba..000000000 --- a/spacy/tests/lang/en/test_contractions.py +++ /dev/null @@ -1,87 +0,0 @@ -# coding: utf-8 -"""Test that tokens are created correctly for contractions.""" - - -from __future__ import unicode_literals - -import pytest - - -def test_tokenizer_handles_basic_contraction(en_tokenizer): - text = "don't giggle" - tokens = en_tokenizer(text) - assert len(tokens) == 3 - assert tokens[1].text == "n't" - text = "i said don't!" - tokens = en_tokenizer(text) - assert len(tokens) == 5 - assert tokens[4].text == "!" - - -@pytest.mark.parametrize('text', ["`ain't", '''"isn't''', "can't!"]) -def test_tokenizer_handles_basic_contraction_punct(en_tokenizer, text): - tokens = en_tokenizer(text) - assert len(tokens) == 3 - - -@pytest.mark.parametrize('text_poss,text', [("Robin's", "Robin"), ("Alexis's", "Alexis")]) -def test_tokenizer_handles_poss_contraction(en_tokenizer, text_poss, text): - tokens = en_tokenizer(text_poss) - assert len(tokens) == 2 - assert tokens[0].text == text - assert tokens[1].text == "'s" - - -@pytest.mark.parametrize('text', ["schools'", "Alexis'"]) -def test_tokenizer_splits_trailing_apos(en_tokenizer, text): - tokens = en_tokenizer(text) - assert len(tokens) == 2 - assert tokens[0].text == text.split("'")[0] - assert tokens[1].text == "'" - - -@pytest.mark.parametrize('text', ["'em", "nothin'", "ol'"]) -def text_tokenizer_doesnt_split_apos_exc(en_tokenizer, text): - tokens = en_tokenizer(text) - assert len(tokens) == 1 - assert tokens[0].text == text - - -@pytest.mark.parametrize('text', ["we'll", "You'll", "there'll"]) -def test_tokenizer_handles_ll_contraction(en_tokenizer, text): - tokens = en_tokenizer(text) - assert len(tokens) == 2 - assert tokens[0].text == text.split("'")[0] - assert tokens[1].text == "'ll" - assert tokens[1].lemma_ == "will" - - -@pytest.mark.parametrize('text_lower,text_title', [("can't", "Can't"), ("ain't", "Ain't")]) -def test_tokenizer_handles_capitalization(en_tokenizer, text_lower, text_title): - tokens_lower = en_tokenizer(text_lower) - tokens_title = en_tokenizer(text_title) - assert tokens_title[0].text == tokens_lower[0].text.title() - assert tokens_lower[0].text == tokens_title[0].text.lower() - assert tokens_lower[1].text == tokens_title[1].text - - -@pytest.mark.parametrize('pron', ["I", "You", "He", "She", "It", "We", "They"]) -@pytest.mark.parametrize('contraction', ["'ll", "'d"]) -def test_tokenizer_keeps_title_case(en_tokenizer, pron, contraction): - tokens = en_tokenizer(pron + contraction) - assert tokens[0].text == pron - assert tokens[1].text == contraction - - -@pytest.mark.parametrize('exc', ["Ill", "ill", "Hell", "hell", "Well", "well"]) -def test_tokenizer_excludes_ambiguous(en_tokenizer, exc): - tokens = en_tokenizer(exc) - assert len(tokens) == 1 - - -@pytest.mark.parametrize('wo_punct,w_punct', [("We've", "``We've"), ("couldn't", "couldn't)")]) -def test_tokenizer_splits_defined_punct(en_tokenizer, wo_punct, w_punct): - tokens = en_tokenizer(wo_punct) - assert len(tokens) == 2 - tokens = en_tokenizer(w_punct) - assert len(tokens) == 3 diff --git a/spacy/tests/lang/en/test_exceptions.py b/spacy/tests/lang/en/test_exceptions.py index 03e738a34..3115354bb 100644 --- a/spacy/tests/lang/en/test_exceptions.py +++ b/spacy/tests/lang/en/test_exceptions.py @@ -1,19 +1,96 @@ # coding: utf-8 -"""Test that tokenizer exceptions are handled correctly.""" - - from __future__ import unicode_literals import pytest +def test_en_tokenizer_handles_basic_contraction(en_tokenizer): + text = "don't giggle" + tokens = en_tokenizer(text) + assert len(tokens) == 3 + assert tokens[1].text == "n't" + text = "i said don't!" + tokens = en_tokenizer(text) + assert len(tokens) == 5 + assert tokens[4].text == "!" + + +@pytest.mark.parametrize('text', ["`ain't", '''"isn't''', "can't!"]) +def test_en_tokenizer_handles_basic_contraction_punct(en_tokenizer, text): + tokens = en_tokenizer(text) + assert len(tokens) == 3 + + +@pytest.mark.parametrize('text_poss,text', [("Robin's", "Robin"), ("Alexis's", "Alexis")]) +def test_en_tokenizer_handles_poss_contraction(en_tokenizer, text_poss, text): + tokens = en_tokenizer(text_poss) + assert len(tokens) == 2 + assert tokens[0].text == text + assert tokens[1].text == "'s" + + +@pytest.mark.parametrize('text', ["schools'", "Alexis'"]) +def test_en_tokenizer_splits_trailing_apos(en_tokenizer, text): + tokens = en_tokenizer(text) + assert len(tokens) == 2 + assert tokens[0].text == text.split("'")[0] + assert tokens[1].text == "'" + + +@pytest.mark.parametrize('text', ["'em", "nothin'", "ol'"]) +def text_tokenizer_doesnt_split_apos_exc(en_tokenizer, text): + tokens = en_tokenizer(text) + assert len(tokens) == 1 + assert tokens[0].text == text + + +@pytest.mark.parametrize('text', ["we'll", "You'll", "there'll"]) +def test_en_tokenizer_handles_ll_contraction(en_tokenizer, text): + tokens = en_tokenizer(text) + assert len(tokens) == 2 + assert tokens[0].text == text.split("'")[0] + assert tokens[1].text == "'ll" + assert tokens[1].lemma_ == "will" + + +@pytest.mark.parametrize('text_lower,text_title', [("can't", "Can't"), ("ain't", "Ain't")]) +def test_en_tokenizer_handles_capitalization(en_tokenizer, text_lower, text_title): + tokens_lower = en_tokenizer(text_lower) + tokens_title = en_tokenizer(text_title) + assert tokens_title[0].text == tokens_lower[0].text.title() + assert tokens_lower[0].text == tokens_title[0].text.lower() + assert tokens_lower[1].text == tokens_title[1].text + + +@pytest.mark.parametrize('pron', ["I", "You", "He", "She", "It", "We", "They"]) +@pytest.mark.parametrize('contraction', ["'ll", "'d"]) +def test_en_tokenizer_keeps_title_case(en_tokenizer, pron, contraction): + tokens = en_tokenizer(pron + contraction) + assert tokens[0].text == pron + assert tokens[1].text == contraction + + +@pytest.mark.parametrize('exc', ["Ill", "ill", "Hell", "hell", "Well", "well"]) +def test_en_tokenizer_excludes_ambiguous(en_tokenizer, exc): + tokens = en_tokenizer(exc) + assert len(tokens) == 1 + + +@pytest.mark.parametrize('wo_punct,w_punct', [("We've", "``We've"), ("couldn't", "couldn't)")]) +def test_en_tokenizer_splits_defined_punct(en_tokenizer, wo_punct, w_punct): + tokens = en_tokenizer(wo_punct) + assert len(tokens) == 2 + tokens = en_tokenizer(w_punct) + assert len(tokens) == 3 + + @pytest.mark.parametrize('text', ["e.g.", "p.m.", "Jan.", "Dec.", "Inc."]) -def test_tokenizer_handles_abbr(en_tokenizer, text): +def test_en_tokenizer_handles_abbr(en_tokenizer, text): tokens = en_tokenizer(text) assert len(tokens) == 1 -def test_tokenizer_handles_exc_in_text(en_tokenizer): +def test_en_tokenizer_handles_exc_in_text(en_tokenizer): text = "It's mediocre i.e. bad." tokens = en_tokenizer(text) assert len(tokens) == 6 @@ -21,7 +98,19 @@ def test_tokenizer_handles_exc_in_text(en_tokenizer): @pytest.mark.parametrize('text', ["1am", "12a.m.", "11p.m.", "4pm"]) -def test_tokenizer_handles_times(en_tokenizer, text): +def test_en_tokenizer_handles_times(en_tokenizer, text): tokens = en_tokenizer(text) assert len(tokens) == 2 assert tokens[1].lemma_ in ["a.m.", "p.m."] + + +@pytest.mark.parametrize('text,norms', [("I'm", ["i", "am"]), ("shan't", ["shall", "not"])]) +def test_en_tokenizer_norm_exceptions(en_tokenizer, text, norms): + tokens = en_tokenizer(text) + assert [token.norm_ for token in tokens] == norms + + +@pytest.mark.parametrize('text,norm', [("radicalised", "radicalized"), ("cuz", "because")]) +def test_en_lex_attrs_norm_exceptions(en_tokenizer, text, norm): + tokens = en_tokenizer(text) + assert tokens[0].norm_ == norm diff --git a/spacy/tests/lang/en/test_indices.py b/spacy/tests/lang/en/test_indices.py index 0ed6ca4dc..c8f4c4b61 100644 --- a/spacy/tests/lang/en/test_indices.py +++ b/spacy/tests/lang/en/test_indices.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals import pytest -def test_simple_punct(en_tokenizer): +def test_en_simple_punct(en_tokenizer): text = "to walk, do foo" tokens = en_tokenizer(text) assert tokens[0].idx == 0 @@ -17,7 +17,7 @@ def test_simple_punct(en_tokenizer): assert tokens[4].idx == 12 -def test_complex_punct(en_tokenizer): +def test_en_complex_punct(en_tokenizer): text = "Tom (D., Ill.)!" tokens = en_tokenizer(text) assert tokens[0].idx == 0 diff --git a/spacy/tests/lang/en/test_lemmatizer.py b/spacy/tests/lang/en/test_lemmatizer.py new file mode 100644 index 000000000..d02ae1700 --- /dev/null +++ b/spacy/tests/lang/en/test_lemmatizer.py @@ -0,0 +1,47 @@ +# coding: utf-8 +from __future__ import unicode_literals + +import pytest + + +@pytest.fixture +def en_lemmatizer(EN): + return EN.Defaults.create_lemmatizer() + + +@pytest.mark.models('en') +@pytest.mark.parametrize('text,lemmas', [("aardwolves", ["aardwolf"]), + ("aardwolf", ["aardwolf"]), + ("planets", ["planet"]), + ("ring", ["ring"]), + ("axes", ["axis", "axe", "ax"])]) +def test_en_lemmatizer_noun_lemmas(en_lemmatizer, text, lemmas): + assert en_lemmatizer.noun(text) == set(lemmas) + + +@pytest.mark.xfail +@pytest.mark.models('en') +def test_en_lemmatizer_base_forms(en_lemmatizer): + assert en_lemmatizer.noun('dive', {'number': 'sing'}) == set(['dive']) + assert en_lemmatizer.noun('dive', {'number': 'plur'}) == set(['diva']) + + +@pytest.mark.models('en') +def test_en_lemmatizer_base_form_verb(en_lemmatizer): + assert en_lemmatizer.verb('saw', {'verbform': 'past'}) == set(['see']) + + +@pytest.mark.models('en') +def test_en_lemmatizer_punct(en_lemmatizer): + assert en_lemmatizer.punct('“') == set(['"']) + assert en_lemmatizer.punct('“') == set(['"']) + + +@pytest.mark.models('en') +def test_en_lemmatizer_lemma_assignment(EN): + text = "Bananas in pyjamas are geese." + doc = EN.make_doc(text) + EN.tensorizer(doc) + assert all(t.lemma_ == '' for t in doc) + EN.tagger(doc) + assert all(t.lemma_ != '' for t in doc) diff --git a/spacy/tests/lang/en/test_models.py b/spacy/tests/lang/en/test_models.py new file mode 100644 index 000000000..4b1cf1f91 --- /dev/null +++ b/spacy/tests/lang/en/test_models.py @@ -0,0 +1,76 @@ +# coding: utf-8 +from __future__ import unicode_literals + +import numpy +import pytest + + +@pytest.fixture +def example(EN): + """ + This is to make sure the model works as expected. The tests make sure that + values are properly set. Tests are not meant to evaluate the content of the + output, only make sure the output is formally okay. + """ + assert EN.entity != None + return EN('There was a stranger standing at the big street talking to herself.') + + +@pytest.mark.models('en') +def test_en_models_tokenization(example): + # tokenization should split the document into tokens + assert len(example) > 1 + + +@pytest.mark.models('en') +def test_en_models_tagging(example): + # if tagging was done properly, pos tags shouldn't be empty + assert example.is_tagged + assert all(t.pos != 0 for t in example) + assert all(t.tag != 0 for t in example) + + +@pytest.mark.models('en') +def test_en_models_parsing(example): + # if parsing was done properly + # - dependency labels shouldn't be empty + # - the head of some tokens should not be root + assert example.is_parsed + assert all(t.dep != 0 for t in example) + assert any(t.dep != i for i,t in enumerate(example)) + + +@pytest.mark.models('en') +def test_en_models_ner(example): + # if ner was done properly, ent_iob shouldn't be empty + assert all([t.ent_iob != 0 for t in example]) + + +@pytest.mark.models('en') +def test_en_models_vectors(example): + # if vectors are available, they should differ on different words + # this isn't a perfect test since this could in principle fail + # in a sane model as well, + # but that's very unlikely and a good indicator if something is wrong + vector0 = example[0].vector + vector1 = example[1].vector + vector2 = example[2].vector + assert not numpy.array_equal(vector0,vector1) + assert not numpy.array_equal(vector0,vector2) + assert not numpy.array_equal(vector1,vector2) + + +@pytest.mark.xfail +@pytest.mark.models('en') +def test_en_models_probs(example): + # if frequencies/probabilities are okay, they should differ for + # different words + # this isn't a perfect test since this could in principle fail + # in a sane model as well, + # but that's very unlikely and a good indicator if something is wrong + prob0 = example[0].prob + prob1 = example[1].prob + prob2 = example[2].prob + assert not prob0 == prob1 + assert not prob0 == prob2 + assert not prob1 == prob2 diff --git a/spacy/tests/lang/en/test_ner.py b/spacy/tests/lang/en/test_ner.py new file mode 100644 index 000000000..8a7838625 --- /dev/null +++ b/spacy/tests/lang/en/test_ner.py @@ -0,0 +1,42 @@ +from __future__ import unicode_literals, print_function +import pytest + +from spacy.attrs import LOWER +from spacy.matcher import Matcher + + +@pytest.mark.models('en') +def test_en_ner_simple_types(EN): + tokens = EN(u'Mr. Best flew to New York on Saturday morning.') + ents = list(tokens.ents) + assert ents[0].start == 1 + assert ents[0].end == 2 + assert ents[0].label_ == 'PERSON' + assert ents[1].start == 4 + assert ents[1].end == 6 + assert ents[1].label_ == 'GPE' + + +@pytest.mark.skip +@pytest.mark.models('en') +def test_en_ner_consistency_bug(EN): + '''Test an arbitrary sequence-consistency bug encountered during speed test''' + tokens = EN(u'Where rap essentially went mainstream, illustrated by seminal Public Enemy, Beastie Boys and L.L. Cool J. tracks.') + tokens = EN(u'''Charity and other short-term aid have buoyed them so far, and a tax-relief bill working its way through Congress would help. But the September 11 Victim Compensation Fund, enacted by Congress to discourage people from filing lawsuits, will determine the shape of their lives for years to come.\n\n''', disable=['ner']) + tokens.ents += tuple(EN.matcher(tokens)) + EN.entity(tokens) + + +@pytest.mark.skip +@pytest.mark.models('en') +def test_en_ner_unit_end_gazetteer(EN): + '''Test a bug in the interaction between the NER model and the gazetteer''' + matcher = Matcher(EN.vocab) + matcher.add('MemberNames', None, [{LOWER: 'cal'}], [{LOWER: 'cal'}, {LOWER: 'henderson'}]) + doc = EN(u'who is cal the manager of?') + if len(list(doc.ents)) == 0: + ents = matcher(doc) + assert len(ents) == 1 + doc.ents += tuple(ents) + EN.entity(doc) + assert list(doc.ents)[0].text == 'cal' diff --git a/spacy/tests/doc/test_noun_chunks.py b/spacy/tests/lang/en/test_noun_chunks.py similarity index 68% rename from spacy/tests/doc/test_noun_chunks.py rename to spacy/tests/lang/en/test_noun_chunks.py index 114a0b0ae..2bfe041f9 100644 --- a/spacy/tests/doc/test_noun_chunks.py +++ b/spacy/tests/lang/en/test_noun_chunks.py @@ -1,15 +1,15 @@ # coding: utf-8 from __future__ import unicode_literals -from ...attrs import HEAD, DEP -from ...symbols import nsubj, dobj, amod, nmod, conj, cc, root -from ...syntax.iterators import english_noun_chunks -from ..util import get_doc +from ....attrs import HEAD, DEP +from ....symbols import nsubj, dobj, amod, nmod, conj, cc, root +from ....lang.en.syntax_iterators import SYNTAX_ITERATORS +from ...util import get_doc import numpy -def test_doc_noun_chunks_not_nested(en_tokenizer): +def test_en_noun_chunks_not_nested(en_tokenizer): text = "Peter has chronic command and control issues" heads = [1, 0, 4, 3, -1, -2, -5] deps = ['nsubj', 'ROOT', 'amod', 'nmod', 'cc', 'conj', 'dobj'] @@ -20,8 +20,8 @@ def test_doc_noun_chunks_not_nested(en_tokenizer): tokens.from_array( [HEAD, DEP], numpy.asarray([[1, nsubj], [0, root], [4, amod], [3, nmod], [-1, cc], - [-2, conj], [-5, dobj]], dtype='int32')) - tokens.noun_chunks_iterator = english_noun_chunks + [-2, conj], [-5, dobj]], dtype='uint64')) + tokens.noun_chunks_iterator = SYNTAX_ITERATORS['noun_chunks'] word_occurred = {} for chunk in tokens.noun_chunks: for word in chunk: diff --git a/spacy/tests/parser/test_noun_chunks.py b/spacy/tests/lang/en/test_parser.py similarity index 59% rename from spacy/tests/parser/test_noun_chunks.py rename to spacy/tests/lang/en/test_parser.py index 5e8c7659a..39d0fce61 100644 --- a/spacy/tests/parser/test_noun_chunks.py +++ b/spacy/tests/lang/en/test_parser.py @@ -1,7 +1,7 @@ # coding: utf-8 from __future__ import unicode_literals -from ..util import get_doc +from ...util import get_doc import pytest @@ -45,32 +45,3 @@ def test_parser_noun_chunks_pp_chunks(en_tokenizer): assert len(chunks) == 2 assert chunks[0].text_with_ws == "A phrase " assert chunks[1].text_with_ws == "another phrase " - - -def test_parser_noun_chunks_standard_de(de_tokenizer): - text = "Eine Tasse steht auf dem Tisch." - heads = [1, 1, 0, -1, 1, -2, -4] - tags = ['ART', 'NN', 'VVFIN', 'APPR', 'ART', 'NN', '$.'] - deps = ['nk', 'sb', 'ROOT', 'mo', 'nk', 'nk', 'punct'] - - tokens = de_tokenizer(text) - doc = get_doc(tokens.vocab, [t.text for t in tokens], tags=tags, deps=deps, heads=heads) - chunks = list(doc.noun_chunks) - assert len(chunks) == 2 - assert chunks[0].text_with_ws == "Eine Tasse " - assert chunks[1].text_with_ws == "dem Tisch " - - -def test_de_extended_chunk(de_tokenizer): - text = "Die Sängerin singt mit einer Tasse Kaffee Arien." - heads = [1, 1, 0, -1, 1, -2, -1, -5, -6] - tags = ['ART', 'NN', 'VVFIN', 'APPR', 'ART', 'NN', 'NN', 'NN', '$.'] - deps = ['nk', 'sb', 'ROOT', 'mo', 'nk', 'nk', 'nk', 'oa', 'punct'] - - tokens = de_tokenizer(text) - doc = get_doc(tokens.vocab, [t.text for t in tokens], tags=tags, deps=deps, heads=heads) - chunks = list(doc.noun_chunks) - assert len(chunks) == 3 - assert chunks[0].text_with_ws == "Die Sängerin " - assert chunks[1].text_with_ws == "einer Tasse Kaffee " - assert chunks[2].text_with_ws == "Arien " diff --git a/spacy/tests/lang/en/test_punct.py b/spacy/tests/lang/en/test_punct.py index d7d5592f4..750008603 100644 --- a/spacy/tests/lang/en/test_punct.py +++ b/spacy/tests/lang/en/test_punct.py @@ -16,14 +16,14 @@ PUNCT_PAIRED = [('(', ')'), ('[', ']'), ('{', '}'), ('*', '*')] @pytest.mark.parametrize('text', ["(", "((", "<"]) -def test_tokenizer_handles_only_punct(en_tokenizer, text): +def test_en_tokenizer_handles_only_punct(en_tokenizer, text): tokens = en_tokenizer(text) assert len(tokens) == len(text) @pytest.mark.parametrize('punct', PUNCT_OPEN) @pytest.mark.parametrize('text', ["Hello"]) -def test_tokenizer_splits_open_punct(en_tokenizer, punct, text): +def test_en_tokenizer_splits_open_punct(en_tokenizer, punct, text): tokens = en_tokenizer(punct + text) assert len(tokens) == 2 assert tokens[0].text == punct @@ -32,7 +32,7 @@ def test_tokenizer_splits_open_punct(en_tokenizer, punct, text): @pytest.mark.parametrize('punct', PUNCT_CLOSE) @pytest.mark.parametrize('text', ["Hello"]) -def test_tokenizer_splits_close_punct(en_tokenizer, punct, text): +def test_en_tokenizer_splits_close_punct(en_tokenizer, punct, text): tokens = en_tokenizer(text + punct) assert len(tokens) == 2 assert tokens[0].text == text @@ -42,7 +42,7 @@ def test_tokenizer_splits_close_punct(en_tokenizer, punct, text): @pytest.mark.parametrize('punct', PUNCT_OPEN) @pytest.mark.parametrize('punct_add', ["`"]) @pytest.mark.parametrize('text', ["Hello"]) -def test_tokenizer_splits_two_diff_open_punct(en_tokenizer, punct, punct_add, text): +def test_en_tokenizer_splits_two_diff_open_punct(en_tokenizer, punct, punct_add, text): tokens = en_tokenizer(punct + punct_add + text) assert len(tokens) == 3 assert tokens[0].text == punct @@ -53,7 +53,7 @@ def test_tokenizer_splits_two_diff_open_punct(en_tokenizer, punct, punct_add, te @pytest.mark.parametrize('punct', PUNCT_CLOSE) @pytest.mark.parametrize('punct_add', ["'"]) @pytest.mark.parametrize('text', ["Hello"]) -def test_tokenizer_splits_two_diff_close_punct(en_tokenizer, punct, punct_add, text): +def test_en_tokenizer_splits_two_diff_close_punct(en_tokenizer, punct, punct_add, text): tokens = en_tokenizer(text + punct + punct_add) assert len(tokens) == 3 assert tokens[0].text == text @@ -63,7 +63,7 @@ def test_tokenizer_splits_two_diff_close_punct(en_tokenizer, punct, punct_add, t @pytest.mark.parametrize('punct', PUNCT_OPEN) @pytest.mark.parametrize('text', ["Hello"]) -def test_tokenizer_splits_same_open_punct(en_tokenizer, punct, text): +def test_en_tokenizer_splits_same_open_punct(en_tokenizer, punct, text): tokens = en_tokenizer(punct + punct + punct + text) assert len(tokens) == 4 assert tokens[0].text == punct @@ -72,7 +72,7 @@ def test_tokenizer_splits_same_open_punct(en_tokenizer, punct, text): @pytest.mark.parametrize('punct', PUNCT_CLOSE) @pytest.mark.parametrize('text', ["Hello"]) -def test_tokenizer_splits_same_close_punct(en_tokenizer, punct, text): +def test_en_tokenizer_splits_same_close_punct(en_tokenizer, punct, text): tokens = en_tokenizer(text + punct + punct + punct) assert len(tokens) == 4 assert tokens[0].text == text @@ -80,14 +80,14 @@ def test_tokenizer_splits_same_close_punct(en_tokenizer, punct, text): @pytest.mark.parametrize('text', ["'The"]) -def test_tokenizer_splits_open_appostrophe(en_tokenizer, text): +def test_en_tokenizer_splits_open_appostrophe(en_tokenizer, text): tokens = en_tokenizer(text) assert len(tokens) == 2 assert tokens[0].text == "'" @pytest.mark.parametrize('text', ["Hello''"]) -def test_tokenizer_splits_double_end_quote(en_tokenizer, text): +def test_en_tokenizer_splits_double_end_quote(en_tokenizer, text): tokens = en_tokenizer(text) assert len(tokens) == 2 tokens_punct = en_tokenizer("''") @@ -96,7 +96,7 @@ def test_tokenizer_splits_double_end_quote(en_tokenizer, text): @pytest.mark.parametrize('punct_open,punct_close', PUNCT_PAIRED) @pytest.mark.parametrize('text', ["Hello"]) -def test_tokenizer_splits_open_close_punct(en_tokenizer, punct_open, +def test_en_tokenizer_splits_open_close_punct(en_tokenizer, punct_open, punct_close, text): tokens = en_tokenizer(punct_open + text + punct_close) assert len(tokens) == 3 @@ -108,7 +108,7 @@ def test_tokenizer_splits_open_close_punct(en_tokenizer, punct_open, @pytest.mark.parametrize('punct_open,punct_close', PUNCT_PAIRED) @pytest.mark.parametrize('punct_open2,punct_close2', [("`", "'")]) @pytest.mark.parametrize('text', ["Hello"]) -def test_tokenizer_two_diff_punct(en_tokenizer, punct_open, punct_close, +def test_en_tokenizer_two_diff_punct(en_tokenizer, punct_open, punct_close, punct_open2, punct_close2, text): tokens = en_tokenizer(punct_open2 + punct_open + text + punct_close + punct_close2) assert len(tokens) == 5 @@ -120,13 +120,13 @@ def test_tokenizer_two_diff_punct(en_tokenizer, punct_open, punct_close, @pytest.mark.parametrize('text,punct', [("(can't", "(")]) -def test_tokenizer_splits_pre_punct_regex(text, punct): +def test_en_tokenizer_splits_pre_punct_regex(text, punct): en_search_prefixes = compile_prefix_regex(TOKENIZER_PREFIXES).search match = en_search_prefixes(text) assert match.group() == punct -def test_tokenizer_splits_bracket_period(en_tokenizer): +def test_en_tokenizer_splits_bracket_period(en_tokenizer): text = "(And a 6a.m. run through Washington Park)." tokens = en_tokenizer(text) assert tokens[len(tokens) - 1].text == "." diff --git a/spacy/tests/parser/test_sbd_prag.py b/spacy/tests/lang/en/test_sbd.py similarity index 57% rename from spacy/tests/parser/test_sbd_prag.py rename to spacy/tests/lang/en/test_sbd.py index ba5571224..8378b186f 100644 --- a/spacy/tests/parser/test_sbd_prag.py +++ b/spacy/tests/lang/en/test_sbd.py @@ -1,25 +1,81 @@ -# encoding: utf-8 +# coding: utf-8 from __future__ import unicode_literals +from ....tokens import Doc +from ...util import get_doc, apply_transition_sequence + import pytest +@pytest.mark.parametrize('text', ["A test sentence"]) +@pytest.mark.parametrize('punct', ['.', '!', '?', '']) +def test_en_sbd_single_punct(en_tokenizer, text, punct): + heads = [2, 1, 0, -1] if punct else [2, 1, 0] + tokens = en_tokenizer(text + punct) + doc = get_doc(tokens.vocab, [t.text for t in tokens], heads=heads) + assert len(doc) == 4 if punct else 3 + assert len(list(doc.sents)) == 1 + assert sum(len(sent) for sent in doc.sents) == len(doc) + + +@pytest.mark.xfail +def test_en_sentence_breaks(en_tokenizer, en_parser): + text = "This is a sentence . This is another one ." + heads = [1, 0, 1, -2, -3, 1, 0, 1, -2, -3] + deps = ['nsubj', 'ROOT', 'det', 'attr', 'punct', 'nsubj', 'ROOT', 'det', + 'attr', 'punct'] + transition = ['L-nsubj', 'S', 'L-det', 'R-attr', 'D', 'R-punct', 'B-ROOT', + 'L-nsubj', 'S', 'L-attr', 'R-attr', 'D', 'R-punct'] + + tokens = en_tokenizer(text) + doc = get_doc(tokens.vocab, [t.text for t in tokens], heads=heads, deps=deps) + apply_transition_sequence(en_parser, doc, transition) + + assert len(list(doc.sents)) == 2 + for token in doc: + assert token.dep != 0 or token.is_space + assert [token.head.i for token in doc ] == [1, 1, 3, 1, 1, 6, 6, 8, 6, 6] + + +# Currently, there's no way of setting the serializer data for the parser +# without loading the models, so we can't remove the model dependency here yet. + +@pytest.mark.xfail +@pytest.mark.models('en') +def test_en_sbd_serialization_projective(EN): + """Test that before and after serialization, the sentence boundaries are + the same.""" + + text = "I bought a couch from IKEA It wasn't very comfortable." + transition = ['L-nsubj', 'S', 'L-det', 'R-dobj', 'D', 'R-prep', 'R-pobj', + 'B-ROOT', 'L-nsubj', 'R-neg', 'D', 'S', 'L-advmod', + 'R-acomp', 'D', 'R-punct'] + + doc = EN.tokenizer(text) + apply_transition_sequence(EN.parser, doc, transition) + doc_serialized = Doc(EN.vocab).from_bytes(doc.to_bytes()) + assert doc.is_parsed == True + assert doc_serialized.is_parsed == True + assert doc.to_bytes() == doc_serialized.to_bytes() + assert [s.text for s in doc.sents] == [s.text for s in doc_serialized.sents] + + TEST_CASES = [ - ("Hello World. My name is Jonas.", ["Hello World.", "My name is Jonas."]), + pytest.mark.xfail(("Hello World. My name is Jonas.", ["Hello World.", "My name is Jonas."])), ("What is your name? My name is Jonas.", ["What is your name?", "My name is Jonas."]), - pytest.mark.xfail(("There it is! I found it.", ["There it is!", "I found it."])), + ("There it is! I found it.", ["There it is!", "I found it."]), ("My name is Jonas E. Smith.", ["My name is Jonas E. Smith."]), ("Please turn to p. 55.", ["Please turn to p. 55."]), ("Were Jane and co. at the party?", ["Were Jane and co. at the party?"]), ("They closed the deal with Pitt, Briggs & Co. at noon.", ["They closed the deal with Pitt, Briggs & Co. at noon."]), - pytest.mark.xfail(("Let's ask Jane and co. They should know.", ["Let's ask Jane and co.", "They should know."])), + ("Let's ask Jane and co. They should know.", ["Let's ask Jane and co.", "They should know."]), ("They closed the deal with Pitt, Briggs & Co. It closed yesterday.", ["They closed the deal with Pitt, Briggs & Co.", "It closed yesterday."]), ("I can see Mt. Fuji from here.", ["I can see Mt. Fuji from here."]), - ("St. Michael's Church is on 5th st. near the light.", ["St. Michael's Church is on 5th st. near the light."]), + pytest.mark.xfail(("St. Michael's Church is on 5th st. near the light.", ["St. Michael's Church is on 5th st. near the light."])), ("That is JFK Jr.'s book.", ["That is JFK Jr.'s book."]), ("I visited the U.S.A. last year.", ["I visited the U.S.A. last year."]), - pytest.mark.xfail(("I live in the E.U. How about you?", ["I live in the E.U.", "How about you?"])), - pytest.mark.xfail(("I live in the U.S. How about you?", ["I live in the U.S.", "How about you?"])), + ("I live in the E.U. How about you?", ["I live in the E.U.", "How about you?"]), + ("I live in the U.S. How about you?", ["I live in the U.S.", "How about you?"]), ("I work for the U.S. Government in Virginia.", ["I work for the U.S. Government in Virginia."]), ("I have lived in the U.S. for 20 years.", ["I have lived in the U.S. for 20 years."]), pytest.mark.xfail(("At 5 a.m. Mr. Smith went to the bank. He left the bank at 6 P.M. Mr. Smith then went to the store.", ["At 5 a.m. Mr. Smith went to the bank.", "He left the bank at 6 P.M.", "Mr. Smith then went to the store."])), @@ -28,7 +84,7 @@ TEST_CASES = [ ("He teaches science (He previously worked for 5 years as an engineer.) at the local University.", ["He teaches science (He previously worked for 5 years as an engineer.) at the local University."]), ("Her email is Jane.Doe@example.com. I sent her an email.", ["Her email is Jane.Doe@example.com.", "I sent her an email."]), ("The site is: https://www.example.50.com/new-site/awesome_content.html. Please check it out.", ["The site is: https://www.example.50.com/new-site/awesome_content.html.", "Please check it out."]), - ("She turned to him, 'This is great.' she said.", ["She turned to him, 'This is great.' she said."]), + pytest.mark.xfail(("She turned to him, 'This is great.' she said.", ["She turned to him, 'This is great.' she said."])), pytest.mark.xfail(('She turned to him, "This is great." she said.', ['She turned to him, "This is great." she said.'])), ('She turned to him, "This is great." She held the book out to show him.', ['She turned to him, "This is great."', "She held the book out to show him."]), ("Hello!! Long time no see.", ["Hello!!", "Long time no see."]), @@ -47,22 +103,22 @@ TEST_CASES = [ ("This is a sentence\ncut off in the middle because pdf.", ["This is a sentence\ncut off in the middle because pdf."]), ("It was a cold \nnight in the city.", ["It was a cold \nnight in the city."]), pytest.mark.xfail(("features\ncontact manager\nevents, activities\n", ["features", "contact manager", "events, activities"])), - ("You can find it at N°. 1026.253.553. That is where the treasure is.", ["You can find it at N°. 1026.253.553.", "That is where the treasure is."]), + pytest.mark.xfail(("You can find it at N°. 1026.253.553. That is where the treasure is.", ["You can find it at N°. 1026.253.553.", "That is where the treasure is."])), ("She works at Yahoo! in the accounting department.", ["She works at Yahoo! in the accounting department."]), - pytest.mark.xfail(("We make a good team, you and I. Did you see Albert I. Jones yesterday?", ["We make a good team, you and I.", "Did you see Albert I. Jones yesterday?"])), + ("We make a good team, you and I. Did you see Albert I. Jones yesterday?", ["We make a good team, you and I.", "Did you see Albert I. Jones yesterday?"]), ("Thoreau argues that by simplifying one’s life, “the laws of the universe will appear less complex. . . .”", ["Thoreau argues that by simplifying one’s life, “the laws of the universe will appear less complex. . . .”"]), - (""""Bohr [...] used the analogy of parallel stairways [...]" (Smith 55).""", ['"Bohr [...] used the analogy of parallel stairways [...]" (Smith 55).']), - pytest.mark.xfail(("If words are left off at the end of a sentence, and that is all that is omitted, indicate the omission with ellipsis marks (preceded and followed by a space) and then indicate the end of the sentence with a period . . . . Next sentence.", ["If words are left off at the end of a sentence, and that is all that is omitted, indicate the omission with ellipsis marks (preceded and followed by a space) and then indicate the end of the sentence with a period . . . .", "Next sentence."])), + pytest.mark.xfail((""""Bohr [...] used the analogy of parallel stairways [...]" (Smith 55).""", ['"Bohr [...] used the analogy of parallel stairways [...]" (Smith 55).'])), + ("If words are left off at the end of a sentence, and that is all that is omitted, indicate the omission with ellipsis marks (preceded and followed by a space) and then indicate the end of the sentence with a period . . . . Next sentence.", ["If words are left off at the end of a sentence, and that is all that is omitted, indicate the omission with ellipsis marks (preceded and followed by a space) and then indicate the end of the sentence with a period . . . .", "Next sentence."]), ("I never meant that.... She left the store.", ["I never meant that....", "She left the store."]), pytest.mark.xfail(("I wasn’t really ... well, what I mean...see . . . what I'm saying, the thing is . . . I didn’t mean it.", ["I wasn’t really ... well, what I mean...see . . . what I'm saying, the thing is . . . I didn’t mean it."])), pytest.mark.xfail(("One further habit which was somewhat weakened . . . was that of combining words into self-interpreting compounds. . . . The practice was not abandoned. . . .", ["One further habit which was somewhat weakened . . . was that of combining words into self-interpreting compounds.", ". . . The practice was not abandoned. . . ."])), pytest.mark.xfail(("Hello world.Today is Tuesday.Mr. Smith went to the store and bought 1,000.That is a lot.", ["Hello world.", "Today is Tuesday.", "Mr. Smith went to the store and bought 1,000.", "That is a lot."])) ] -@pytest.mark.slow -@pytest.mark.models +@pytest.mark.skip +@pytest.mark.models('en') @pytest.mark.parametrize('text,expected_sents', TEST_CASES) -def test_parser_sbd_prag(EN, text, expected_sents): +def test_en_sbd_prag(EN, text, expected_sents): """SBD tests from Pragmatic Segmenter""" doc = EN(text) sents = [] diff --git a/spacy/tests/lang/en/test_tagger.py b/spacy/tests/lang/en/test_tagger.py new file mode 100644 index 000000000..47a093b99 --- /dev/null +++ b/spacy/tests/lang/en/test_tagger.py @@ -0,0 +1,61 @@ +# coding: utf-8 +from __future__ import unicode_literals + +from ....parts_of_speech import SPACE +from ...util import get_doc + +import six +import pytest + + +def test_en_tagger_load_morph_exc(en_tokenizer): + text = "I like his style." + tags = ['PRP', 'VBP', 'PRP$', 'NN', '.'] + morph_exc = {'VBP': {'like': {'L': 'luck'}}} + en_tokenizer.vocab.morphology.load_morph_exceptions(morph_exc) + tokens = en_tokenizer(text) + doc = get_doc(tokens.vocab, [t.text for t in tokens], tags=tags) + assert doc[1].tag_ == 'VBP' + assert doc[1].lemma_ == 'luck' + + +@pytest.mark.models('en') +def test_tag_names(EN): + text = "I ate pizzas with anchovies." + doc = EN(text, disable=['parser']) + assert type(doc[2].pos) == int + assert isinstance(doc[2].pos_, six.text_type) + assert type(doc[2].dep) == int + assert isinstance(doc[2].dep_, six.text_type) + assert doc[2].tag_ == u'NNS' + + +@pytest.mark.xfail +@pytest.mark.models('en') +def test_en_tagger_spaces(EN): + """Ensure spaces are assigned the POS tag SPACE""" + text = "Some\nspaces are\tnecessary." + doc = EN(text, disable=['parser']) + assert doc[0].pos != SPACE + assert doc[0].pos_ != 'SPACE' + assert doc[1].pos == SPACE + assert doc[1].pos_ == 'SPACE' + assert doc[1].tag_ == 'SP' + assert doc[2].pos != SPACE + assert doc[3].pos != SPACE + assert doc[4].pos == SPACE + + +@pytest.mark.xfail +@pytest.mark.models('en') +def test_en_tagger_return_char(EN): + """Ensure spaces are assigned the POS tag SPACE""" + text = ('hi Aaron,\r\n\r\nHow is your schedule today, I was wondering if ' + 'you had time for a phone\r\ncall this afternoon?\r\n\r\n\r\n') + tokens = EN(text) + for token in tokens: + if token.is_space: + assert token.pos == SPACE + assert tokens[3].text == '\r\n\r\n' + assert tokens[3].is_space + assert tokens[3].pos == SPACE diff --git a/spacy/tests/lang/en/test_text.py b/spacy/tests/lang/en/test_text.py index 1769f1262..a2ffaf7ea 100644 --- a/spacy/tests/lang/en/test_text.py +++ b/spacy/tests/lang/en/test_text.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals import pytest -def test_tokenizer_handles_long_text(en_tokenizer): +def test_en_tokenizer_handles_long_text(en_tokenizer): text = """Tributes pour in for late British Labour Party leader Tributes poured in from around the world Thursday @@ -30,12 +30,11 @@ untimely death" of the rapier-tongued Scottish barrister and parliamentarian. ("""'Me too!', Mr. P. Delaware cried. """, 11), ("They ran about 10km.", 6), pytest.mark.xfail(("But then the 6,000-year ice age came...", 10))]) -def test_tokenizer_handles_cnts(en_tokenizer, text, length): +def test_en_tokenizer_handles_cnts(en_tokenizer, text, length): tokens = en_tokenizer(text) assert len(tokens) == length - @pytest.mark.parametrize('text,match', [ ('10', True), ('1', True), ('10,000', True), ('10,00', True), ('999.0', True), ('one', True), ('two', True), ('billion', True), diff --git a/spacy/tests/lang/fr/test_lemmatization.py b/spacy/tests/lang/fr/test_lemmatization.py index c009e72c0..bcd8d4600 100644 --- a/spacy/tests/lang/fr/test_lemmatization.py +++ b/spacy/tests/lang/fr/test_lemmatization.py @@ -1,37 +1,33 @@ # coding: utf-8 - from __future__ import unicode_literals import pytest -@pytest.mark.models +@pytest.mark.models('fr') def test_lemmatizer_verb(FR): - text = "Qu'est-ce que tu fais?" - tokens = FR(text) + tokens = FR("Qu'est-ce que tu fais?") assert tokens[0].lemma_ == "que" assert tokens[1].lemma_ == "être" assert tokens[5].lemma_ == "faire" -@pytest.mark.models + +@pytest.mark.models('fr') @pytest.mark.xfail(reason="sont tagged as AUX") def test_lemmatizer_noun_verb_2(FR): - text = "Les abaissements de température sont gênants." - tokens = FR(text) + tokens = FR("Les abaissements de température sont gênants.") assert tokens[4].lemma_ == "être" -@pytest.mark.models + +@pytest.mark.models('fr') @pytest.mark.xfail(reason="Costaricienne TAG is PROPN instead of NOUN and spacy don't lemmatize PROPN") -def test_lemmatizer_noun(FR): - text = "il y a des Costaricienne." - tokens = FR(text) +def test_lemmatizer_noun(model): + tokens = FR("il y a des Costaricienne.") assert tokens[4].lemma_ == "Costaricain" -@pytest.mark.models + +@pytest.mark.models('fr') def test_lemmatizer_noun_2(FR): - text = "Les abaissements de température sont gênants." - tokens = FR(text) + tokens = FR("Les abaissements de température sont gênants.") assert tokens[1].lemma_ == "abaissement" assert tokens[5].lemma_ == "gênant" - - diff --git a/spacy/tests/lang/hu/test_tokenizer.py b/spacy/tests/lang/hu/test_tokenizer.py index d88b7b7b7..5845b8614 100644 --- a/spacy/tests/lang/hu/test_tokenizer.py +++ b/spacy/tests/lang/hu/test_tokenizer.py @@ -5,11 +5,11 @@ import pytest DEFAULT_TESTS = [ ('N. kormányzósági\nszékhely.', ['N.', 'kormányzósági', 'székhely', '.']), - ('A .hu egy tld.', ['A', '.hu', 'egy', 'tld', '.']), + pytest.mark.xfail(('A .hu egy tld.', ['A', '.hu', 'egy', 'tld', '.'])), ('Az egy.ketto pelda.', ['Az', 'egy.ketto', 'pelda', '.']), ('A pl. rovidites.', ['A', 'pl.', 'rovidites', '.']), ('A S.M.A.R.T. szo.', ['A', 'S.M.A.R.T.', 'szo', '.']), - ('A .hu.', ['A', '.hu', '.']), + pytest.mark.xfail(('A .hu.', ['A', '.hu', '.'])), ('Az egy.ketto.', ['Az', 'egy.ketto', '.']), ('A pl.', ['A', 'pl.']), ('A S.M.A.R.T.', ['A', 'S.M.A.R.T.']), @@ -18,7 +18,9 @@ DEFAULT_TESTS = [ ('Valami ...van...', ['Valami', '...', 'van', '...']), ('Valami...', ['Valami', '...']), ('Valami ...', ['Valami', '...']), - ('Valami ... más.', ['Valami', '...', 'más', '.']) + ('Valami ... más.', ['Valami', '...', 'más', '.']), + ('Soha nem lesz!', ['Soha', 'nem', 'lesz', '!']), + ('Soha nem lesz?', ['Soha', 'nem', 'lesz', '?']) ] HYPHEN_TESTS = [ @@ -225,11 +227,11 @@ QUOTE_TESTS = [ DOT_TESTS = [ ('N. kormányzósági\nszékhely.', ['N.', 'kormányzósági', 'székhely', '.']), - ('A .hu egy tld.', ['A', '.hu', 'egy', 'tld', '.']), + pytest.mark.xfail(('A .hu egy tld.', ['A', '.hu', 'egy', 'tld', '.'])), ('Az egy.ketto pelda.', ['Az', 'egy.ketto', 'pelda', '.']), ('A pl. rövidítés.', ['A', 'pl.', 'rövidítés', '.']), ('A S.M.A.R.T. szó.', ['A', 'S.M.A.R.T.', 'szó', '.']), - ('A .hu.', ['A', '.hu', '.']), + pytest.mark.xfail(('A .hu.', ['A', '.hu', '.'])), ('Az egy.ketto.', ['Az', 'egy.ketto', '.']), ('A pl.', ['A', 'pl.']), ('A S.M.A.R.T.', ['A', 'S.M.A.R.T.']), @@ -241,6 +243,24 @@ DOT_TESTS = [ ('Valami ... más.', ['Valami', '...', 'más', '.']) ] +TYPO_TESTS = [ + ( + 'Ez egy mondat vége.Ez egy másik eleje.', ['Ez', 'egy', 'mondat', 'vége', '.', 'Ez', 'egy', 'másik', 'eleje', '.']), + ('Ez egy mondat vége .Ez egy másik eleje.', + ['Ez', 'egy', 'mondat', 'vége', '.', 'Ez', 'egy', 'másik', 'eleje', '.']), + ( + 'Ez egy mondat vége!ez egy másik eleje.', ['Ez', 'egy', 'mondat', 'vége', '!', 'ez', 'egy', 'másik', 'eleje', '.']), + ('Ez egy mondat vége !ez egy másik eleje.', + ['Ez', 'egy', 'mondat', 'vége', '!', 'ez', 'egy', 'másik', 'eleje', '.']), + ( + 'Ez egy mondat vége?Ez egy másik eleje.', ['Ez', 'egy', 'mondat', 'vége', '?', 'Ez', 'egy', 'másik', 'eleje', '.']), + ('Ez egy mondat vége ?Ez egy másik eleje.', + ['Ez', 'egy', 'mondat', 'vége', '?', 'Ez', 'egy', 'másik', 'eleje', '.']), + ('egy,kettő', ['egy', ',', 'kettő']), + ('egy ,kettő', ['egy', ',', 'kettő']), + ('egy :kettő', ['egy', ':', 'kettő']), +] + WIKI_TESTS = [ ('!"', ['!', '"']), ('lány"a', ['lány', '"', 'a']), @@ -253,7 +273,7 @@ WIKI_TESTS = [ ('cérium(IV)-oxid', ['cérium', '(', 'IV', ')', '-oxid']) ] -TESTCASES = DEFAULT_TESTS + DOT_TESTS + QUOTE_TESTS + NUMBER_TESTS + HYPHEN_TESTS + WIKI_TESTS +TESTCASES = DEFAULT_TESTS + DOT_TESTS + QUOTE_TESTS + NUMBER_TESTS + HYPHEN_TESTS + WIKI_TESTS + TYPO_TESTS @pytest.mark.parametrize('text,expected_tokens', TESTCASES) diff --git a/spacy/tests/matcher/__init__.py b/spacy/tests/matcher/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/spacy/tests/matcher/test_entity_id.py b/spacy/tests/matcher/test_entity_id.py deleted file mode 100644 index 9982a3f44..000000000 --- a/spacy/tests/matcher/test_entity_id.py +++ /dev/null @@ -1,53 +0,0 @@ -# coding: utf-8 -from __future__ import unicode_literals - -from ...matcher import Matcher -from ...attrs import ORTH -from ..util import get_doc - -import pytest - - -@pytest.mark.parametrize('words,entity', [ - (["Test", "Entity"], "TestEntity")]) -def test_matcher_add_empty_entity(en_vocab, words, entity): - matcher = Matcher(en_vocab) - matcher.add_entity(entity) - doc = get_doc(en_vocab, words) - assert matcher.n_patterns == 0 - assert matcher(doc) == [] - - -@pytest.mark.parametrize('entity1,entity2,attrs', [ - ("TestEntity", "TestEntity2", {"Hello": "World"})]) -def test_matcher_get_entity_attrs(en_vocab, entity1, entity2, attrs): - matcher = Matcher(en_vocab) - matcher.add_entity(entity1) - assert matcher.get_entity(entity1) == {} - matcher.add_entity(entity2, attrs=attrs) - assert matcher.get_entity(entity2) == attrs - assert matcher.get_entity(entity1) == {} - - -@pytest.mark.parametrize('words,entity,attrs', - [(["Test", "Entity"], "TestEntity", {"Hello": "World"})]) -def test_matcher_get_entity_via_match(en_vocab, words, entity, attrs): - matcher = Matcher(en_vocab) - matcher.add_entity(entity, attrs=attrs) - doc = get_doc(en_vocab, words) - assert matcher.n_patterns == 0 - assert matcher(doc) == [] - - matcher.add_pattern(entity, [{ORTH: words[0]}, {ORTH: words[1]}]) - assert matcher.n_patterns == 1 - - matches = matcher(doc) - assert len(matches) == 1 - assert len(matches[0]) == 4 - - ent_id, label, start, end = matches[0] - assert ent_id == matcher.vocab.strings[entity] - assert label == 0 - assert start == 0 - assert end == 2 - assert matcher.get_entity(ent_id) == attrs diff --git a/spacy/tests/parser/test_beam_parse.py b/spacy/tests/parser/test_beam_parse.py new file mode 100644 index 000000000..da5f43d5e --- /dev/null +++ b/spacy/tests/parser/test_beam_parse.py @@ -0,0 +1,10 @@ +import spacy +import pytest + +@pytest.mark.models +def test_beam_parse(): + nlp = spacy.load('en_core_web_sm') + doc = nlp(u'Australia is a country', disable=['ner']) + ents = nlp.entity(doc, beam_width=2) + print(ents) + diff --git a/spacy/tests/parser/test_ner.py b/spacy/tests/parser/test_ner.py index f06417e52..e74c9ccb0 100644 --- a/spacy/tests/parser/test_ner.py +++ b/spacy/tests/parser/test_ner.py @@ -1,53 +1,73 @@ -from __future__ import unicode_literals, print_function +from __future__ import unicode_literals + import pytest -from spacy.attrs import LOWER -from spacy.matcher import Matcher +from ...vocab import Vocab +from ...syntax.ner import BiluoPushDown +from ...gold import GoldParse +from ...tokens import Doc -@pytest.mark.models -def test_simple_types(EN): - tokens = EN(u'Mr. Best flew to New York on Saturday morning.') - ents = list(tokens.ents) - assert ents[0].start == 1 - assert ents[0].end == 2 - assert ents[0].label_ == 'PERSON' - assert ents[1].start == 4 - assert ents[1].end == 6 - assert ents[1].label_ == 'GPE' +@pytest.fixture +def vocab(): + return Vocab() -@pytest.mark.models -def test_consistency_bug(EN): - '''Test an arbitrary sequence-consistency bug encountered during speed test''' - tokens = EN(u'Where rap essentially went mainstream, illustrated by seminal Public Enemy, Beastie Boys and L.L. Cool J. tracks.') - - tokens = EN(u'''Charity and other short-term aid have buoyed them so far, and a tax-relief bill working its way through Congress would help. But the September 11 Victim Compensation Fund, enacted by Congress to discourage people from filing lawsuits, will determine the shape of their lives for years to come.\n\n''', entity=False) - tokens.ents += tuple(EN.matcher(tokens)) - EN.entity(tokens) +@pytest.fixture +def doc(vocab): + return Doc(vocab, words=['Casey', 'went', 'to', 'New', 'York', '.']) -@pytest.mark.models -def test_unit_end_gazetteer(EN): - '''Test a bug in the interaction between the NER model and the gazetteer''' - matcher = Matcher(EN.vocab, - {'MemberNames': - ('PERSON', {}, - [ - [{LOWER: 'cal'}], - [{LOWER: 'cal'}, {LOWER: 'henderson'}], - ] - ) - } - ) - - doc = EN(u'who is cal the manager of?') - if len(list(doc.ents)) == 0: - ents = matcher(doc) - assert len(ents) == 1 - doc.ents += tuple(ents) - EN.entity(doc) - assert list(doc.ents)[0].text == 'cal' +@pytest.fixture +def entity_annots(doc): + casey = doc[0:1] + ny = doc[3:5] + return [(casey.start_char, casey.end_char, 'PERSON'), + (ny.start_char, ny.end_char, 'GPE')] - +@pytest.fixture +def entity_types(entity_annots): + return sorted(set([label for (s, e, label) in entity_annots])) + + +@pytest.fixture +def tsys(vocab, entity_types): + actions = BiluoPushDown.get_actions(entity_types=entity_types) + return BiluoPushDown(vocab.strings, actions) + + +def test_get_oracle_moves(tsys, doc, entity_annots): + gold = GoldParse(doc, entities=entity_annots) + tsys.preprocess_gold(gold) + act_classes = tsys.get_oracle_sequence(doc, gold) + names = [tsys.get_class_name(act) for act in act_classes] + assert names == ['U-PERSON', 'O', 'O', 'B-GPE', 'L-GPE', 'O'] + +def test_get_oracle_moves_negative_entities(tsys, doc, entity_annots): + entity_annots = [(s, e, '!' + label) for s, e, label in entity_annots] + gold = GoldParse(doc, entities=entity_annots) + for i, tag in enumerate(gold.ner): + if tag == 'L-!GPE': + gold.ner[i] = '-' + tsys.preprocess_gold(gold) + act_classes = tsys.get_oracle_sequence(doc, gold) + names = [tsys.get_class_name(act) for act in act_classes] + + +def test_get_oracle_moves_negative_entities2(tsys, vocab): + doc = Doc(vocab, words=['A', 'B', 'C', 'D']) + gold = GoldParse(doc, entities=[]) + gold.ner = ['B-!PERSON', 'L-!PERSON', 'B-!PERSON', 'L-!PERSON'] + tsys.preprocess_gold(gold) + act_classes = tsys.get_oracle_sequence(doc, gold) + names = [tsys.get_class_name(act) for act in act_classes] + + +def test_get_oracle_moves_negative_O(tsys, vocab): + doc = Doc(vocab, words=['A', 'B', 'C', 'D']) + gold = GoldParse(doc, entities=[]) + gold.ner = ['O', '!O', 'O', '!O'] + tsys.preprocess_gold(gold) + act_classes = tsys.get_oracle_sequence(doc, gold) + names = [tsys.get_class_name(act) for act in act_classes] diff --git a/spacy/tests/parser/test_neural_parser.py b/spacy/tests/parser/test_neural_parser.py index 1cf122be8..42b55745f 100644 --- a/spacy/tests/parser/test_neural_parser.py +++ b/spacy/tests/parser/test_neural_parser.py @@ -1,7 +1,6 @@ # coding: utf8 from __future__ import unicode_literals from thinc.neural import Model -from mock import Mock import pytest import numpy @@ -36,7 +35,7 @@ def parser(vocab, arc_eager): @pytest.fixture def model(arc_eager, tok2vec): - return Parser.Model(arc_eager.n_moves, token_vector_width=tok2vec.nO) + return Parser.Model(arc_eager.n_moves, token_vector_width=tok2vec.nO)[0] @pytest.fixture def doc(vocab): @@ -45,36 +44,37 @@ def doc(vocab): @pytest.fixture def gold(doc): return GoldParse(doc, heads=[1, 1, 1], deps=['L', 'ROOT', 'R']) + + def test_can_init_nn_parser(parser): assert parser.model is None def test_build_model(parser): - parser.model = Parser.Model(parser.moves.n_moves) + parser.model = Parser.Model(parser.moves.n_moves)[0] assert parser.model is not None def test_predict_doc(parser, tok2vec, model, doc): - state = {} - state['tokvecs'] = tok2vec([doc]) + doc.tensor = tok2vec([doc])[0] parser.model = model - parser(doc, state=state) + parser(doc) def test_update_doc(parser, tok2vec, model, doc, gold): parser.model = model tokvecs, bp_tokvecs = tok2vec.begin_update([doc]) - state = {'tokvecs': tokvecs, 'bp_tokvecs': bp_tokvecs} - state = parser.update(doc, gold, state=state) - loss1 = state['parser_loss'] - assert loss1 > 0 - state = parser.update(doc, gold, state=state) - loss2 = state['parser_loss'] - assert loss2 == loss1 + d_tokvecs = parser.update(([doc], tokvecs), [gold]) + assert d_tokvecs[0].shape == tokvecs[0].shape def optimize(weights, gradient, key=None): weights -= 0.001 * gradient - state = parser.update(doc, gold, sgd=optimize, state=state) - loss3 = state['parser_loss'] - state = parser.update(doc, gold, sgd=optimize, state=state) - lossr = state['parser_loss'] - assert loss3 < loss2 + bp_tokvecs(d_tokvecs, sgd=optimize) + assert d_tokvecs[0].sum() == 0. + + +def test_predict_doc_beam(parser, tok2vec, model, doc): + doc.tensor = tok2vec([doc])[0] + parser.model = model + parser(doc, beam_width=32, beam_density=0.001) + for word in doc: + print(word.text, word.head, word.dep_) diff --git a/spacy/tests/parser/test_nonproj.py b/spacy/tests/parser/test_nonproj.py index 8161d6fc3..237f0debd 100644 --- a/spacy/tests/parser/test_nonproj.py +++ b/spacy/tests/parser/test_nonproj.py @@ -2,7 +2,8 @@ from __future__ import unicode_literals from ...syntax.nonproj import ancestors, contains_cycle, is_nonproj_arc -from ...syntax.nonproj import is_nonproj_tree, PseudoProjectivity +from ...syntax.nonproj import is_nonproj_tree +from ...syntax import nonproj from ...attrs import DEP, HEAD from ..util import get_doc @@ -75,7 +76,7 @@ def test_parser_pseudoprojectivity(en_tokenizer): tokens = en_tokenizer('whatever ' * len(proj_heads)) rel_proj_heads = [head-i for i, head in enumerate(proj_heads)] doc = get_doc(tokens.vocab, [t.text for t in tokens], deps=deco_labels, heads=rel_proj_heads) - PseudoProjectivity.deprojectivize(doc) + nonproj.deprojectivize(doc) return [t.head.i for t in doc], [token.dep_ for token in doc] tree = [1, 2, 2] @@ -85,18 +86,18 @@ def test_parser_pseudoprojectivity(en_tokenizer): labels = ['det', 'nsubj', 'root', 'det', 'dobj', 'aux', 'nsubj', 'acl', 'punct'] labels2 = ['advmod', 'root', 'det', 'nsubj', 'advmod', 'det', 'dobj', 'det', 'nmod', 'aux', 'nmod', 'advmod', 'det', 'amod', 'punct'] - assert(PseudoProjectivity.decompose('X||Y') == ('X','Y')) - assert(PseudoProjectivity.decompose('X') == ('X','')) - assert(PseudoProjectivity.is_decorated('X||Y') == True) - assert(PseudoProjectivity.is_decorated('X') == False) + assert(nonproj.decompose('X||Y') == ('X','Y')) + assert(nonproj.decompose('X') == ('X','')) + assert(nonproj.is_decorated('X||Y') == True) + assert(nonproj.is_decorated('X') == False) - PseudoProjectivity._lift(0, tree) + nonproj._lift(0, tree) assert(tree == [2, 2, 2]) - assert(PseudoProjectivity._get_smallest_nonproj_arc(nonproj_tree) == 7) - assert(PseudoProjectivity._get_smallest_nonproj_arc(nonproj_tree2) == 10) + assert(nonproj._get_smallest_nonproj_arc(nonproj_tree) == 7) + assert(nonproj._get_smallest_nonproj_arc(nonproj_tree2) == 10) - proj_heads, deco_labels = PseudoProjectivity.projectivize(nonproj_tree, labels) + proj_heads, deco_labels = nonproj.projectivize(nonproj_tree, labels) assert(proj_heads == [1, 2, 2, 4, 5, 2, 7, 5, 2]) assert(deco_labels == ['det', 'nsubj', 'root', 'det', 'dobj', 'aux', 'nsubj', 'acl||dobj', 'punct']) @@ -105,7 +106,7 @@ def test_parser_pseudoprojectivity(en_tokenizer): assert(deproj_heads == nonproj_tree) assert(undeco_labels == labels) - proj_heads, deco_labels = PseudoProjectivity.projectivize(nonproj_tree2, labels2) + proj_heads, deco_labels = nonproj.projectivize(nonproj_tree2, labels2) assert(proj_heads == [1, 1, 3, 1, 5, 6, 9, 8, 6, 1, 9, 12, 13, 10, 1]) assert(deco_labels == ['advmod||aux', 'root', 'det', 'nsubj', 'advmod', 'det', 'dobj', 'det', 'nmod', 'aux', 'nmod||dobj', diff --git a/spacy/tests/parser/test_sbd.py b/spacy/tests/parser/test_sbd.py deleted file mode 100644 index 4fa20c900..000000000 --- a/spacy/tests/parser/test_sbd.py +++ /dev/null @@ -1,60 +0,0 @@ -# coding: utf-8 -from __future__ import unicode_literals - -from ...tokens import Doc -from ..util import get_doc, apply_transition_sequence - -import pytest - - -@pytest.mark.parametrize('text', ["A test sentence"]) -@pytest.mark.parametrize('punct', ['.', '!', '?', '']) -def test_parser_sbd_single_punct(en_tokenizer, text, punct): - heads = [2, 1, 0, -1] if punct else [2, 1, 0] - tokens = en_tokenizer(text + punct) - doc = get_doc(tokens.vocab, [t.text for t in tokens], heads=heads) - assert len(doc) == 4 if punct else 3 - assert len(list(doc.sents)) == 1 - assert sum(len(sent) for sent in doc.sents) == len(doc) - - -@pytest.mark.xfail -def test_parser_sentence_breaks(en_tokenizer, en_parser): - text = "This is a sentence . This is another one ." - heads = [1, 0, 1, -2, -3, 1, 0, 1, -2, -3] - deps = ['nsubj', 'ROOT', 'det', 'attr', 'punct', 'nsubj', 'ROOT', 'det', - 'attr', 'punct'] - transition = ['L-nsubj', 'S', 'L-det', 'R-attr', 'D', 'R-punct', 'B-ROOT', - 'L-nsubj', 'S', 'L-attr', 'R-attr', 'D', 'R-punct'] - - tokens = en_tokenizer(text) - doc = get_doc(tokens.vocab, [t.text for t in tokens], heads=heads, deps=deps) - apply_transition_sequence(en_parser, doc, transition) - - assert len(list(doc.sents)) == 2 - for token in doc: - assert token.dep != 0 or token.is_space - assert [token.head.i for token in doc ] == [1, 1, 3, 1, 1, 6, 6, 8, 6, 6] - - -# Currently, there's no way of setting the serializer data for the parser -# without loading the models, so we can't remove the model dependency here yet. - -@pytest.mark.xfail -@pytest.mark.models -def test_parser_sbd_serialization_projective(EN): - """Test that before and after serialization, the sentence boundaries are - the same.""" - - text = "I bought a couch from IKEA It wasn't very comfortable." - transition = ['L-nsubj', 'S', 'L-det', 'R-dobj', 'D', 'R-prep', 'R-pobj', - 'B-ROOT', 'L-nsubj', 'R-neg', 'D', 'S', 'L-advmod', - 'R-acomp', 'D', 'R-punct'] - - doc = EN.tokenizer(text) - apply_transition_sequence(EN.parser, doc, transition) - doc_serialized = Doc(EN.vocab).from_bytes(doc.to_bytes()) - assert doc.is_parsed == True - assert doc_serialized.is_parsed == True - assert doc.to_bytes() == doc_serialized.to_bytes() - assert [s.text for s in doc.sents] == [s.text for s in doc_serialized.sents] diff --git a/spacy/tests/parser/test_to_from_bytes_disk.py b/spacy/tests/parser/test_to_from_bytes_disk.py new file mode 100644 index 000000000..b0a10fa8e --- /dev/null +++ b/spacy/tests/parser/test_to_from_bytes_disk.py @@ -0,0 +1,28 @@ +import pytest + +from ...pipeline import NeuralDependencyParser + + +@pytest.fixture +def parser(en_vocab): + parser = NeuralDependencyParser(en_vocab) + parser.add_label('nsubj') + parser.model, cfg = parser.Model(parser.moves.n_moves) + parser.cfg.update(cfg) + return parser + + +@pytest.fixture +def blank_parser(en_vocab): + parser = NeuralDependencyParser(en_vocab) + return parser + + +def test_to_from_bytes(parser, blank_parser): + assert parser.model is not True + assert blank_parser.model is True + assert blank_parser.moves.n_moves != parser.moves.n_moves + bytes_data = parser.to_bytes() + blank_parser.from_bytes(bytes_data) + assert blank_parser.model is not True + assert blank_parser.moves.n_moves == parser.moves.n_moves diff --git a/spacy/tests/regression/test_issue118.py b/spacy/tests/regression/test_issue118.py index ffdade1d0..b4e1f02b2 100644 --- a/spacy/tests/regression/test_issue118.py +++ b/spacy/tests/regression/test_issue118.py @@ -2,15 +2,14 @@ from __future__ import unicode_literals from ...matcher import Matcher -from ...attrs import ORTH, LOWER import pytest -pattern1 = [[{LOWER: 'celtics'}], [{LOWER: 'boston'}, {LOWER: 'celtics'}]] -pattern2 = [[{LOWER: 'boston'}, {LOWER: 'celtics'}], [{LOWER: 'celtics'}]] -pattern3 = [[{LOWER: 'boston'}], [{LOWER: 'boston'}, {LOWER: 'celtics'}]] -pattern4 = [[{LOWER: 'boston'}, {LOWER: 'celtics'}], [{LOWER: 'boston'}]] +pattern1 = [[{'LOWER': 'celtics'}], [{'LOWER': 'boston'}, {'LOWER': 'celtics'}]] +pattern2 = [[{'LOWER': 'boston'}, {'LOWER': 'celtics'}], [{'LOWER': 'celtics'}]] +pattern3 = [[{'LOWER': 'boston'}], [{'LOWER': 'boston'}, {'LOWER': 'celtics'}]] +pattern4 = [[{'LOWER': 'boston'}, {'LOWER': 'celtics'}], [{'LOWER': 'boston'}]] @pytest.fixture @@ -24,10 +23,11 @@ def doc(en_tokenizer): def test_issue118(doc, pattern): """Test a bug that arose from having overlapping matches""" ORG = doc.vocab.strings['ORG'] - matcher = Matcher(doc.vocab, {'BostonCeltics': ('ORG', {}, pattern)}) + matcher = Matcher(doc.vocab) + matcher.add("BostonCeltics", None, *pattern) assert len(list(doc.ents)) == 0 - matches = [(ent_type, start, end) for ent_id, ent_type, start, end in matcher(doc)] + matches = [(ORG, start, end) for _, start, end in matcher(doc)] assert matches == [(ORG, 9, 11), (ORG, 10, 11)] doc.ents = matches[:1] ents = list(doc.ents) @@ -41,10 +41,11 @@ def test_issue118(doc, pattern): def test_issue118_prefix_reorder(doc, pattern): """Test a bug that arose from having overlapping matches""" ORG = doc.vocab.strings['ORG'] - matcher = Matcher(doc.vocab, {'BostonCeltics': ('ORG', {}, pattern)}) + matcher = Matcher(doc.vocab) + matcher.add('BostonCeltics', None, *pattern) assert len(list(doc.ents)) == 0 - matches = [(ent_type, start, end) for ent_id, ent_type, start, end in matcher(doc)] + matches = [(ORG, start, end) for _, start, end in matcher(doc)] doc.ents += tuple(matches)[1:] assert matches == [(ORG, 9, 10), (ORG, 9, 11)] ents = doc.ents diff --git a/spacy/tests/regression/test_issue242.py b/spacy/tests/regression/test_issue242.py index a4acf04b3..b5909fe65 100644 --- a/spacy/tests/regression/test_issue242.py +++ b/spacy/tests/regression/test_issue242.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals from ...matcher import Matcher -from ...attrs import LOWER import pytest @@ -10,14 +9,14 @@ import pytest def test_issue242(en_tokenizer): """Test overlapping multi-word phrases.""" text = "There are different food safety standards in different countries." - patterns = [[{LOWER: 'food'}, {LOWER: 'safety'}], - [{LOWER: 'safety'}, {LOWER: 'standards'}]] + patterns = [[{'LOWER': 'food'}, {'LOWER': 'safety'}], + [{'LOWER': 'safety'}, {'LOWER': 'standards'}]] doc = en_tokenizer(text) matcher = Matcher(doc.vocab) - matcher.add('FOOD', 'FOOD', {}, patterns) + matcher.add('FOOD', None, *patterns) - matches = [(ent_type, start, end) for ent_id, ent_type, start, end in matcher(doc)] + matches = [(ent_type, start, end) for ent_type, start, end in matcher(doc)] doc.ents += tuple(matches) match1, match2 = matches assert match1[1] == 3 diff --git a/spacy/tests/regression/test_issue401.py b/spacy/tests/regression/test_issue401.py index 9d862cc65..e5b72d472 100644 --- a/spacy/tests/regression/test_issue401.py +++ b/spacy/tests/regression/test_issue401.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import pytest -@pytest.mark.models +@pytest.mark.models('en') @pytest.mark.parametrize('text,i', [("Jane's got a new car", 1), ("Jane thinks that's a nice car", 3)]) def test_issue401(EN, text, i): diff --git a/spacy/tests/regression/test_issue429.py b/spacy/tests/regression/test_issue429.py index 5b76f05e6..1baa9a1db 100644 --- a/spacy/tests/regression/test_issue429.py +++ b/spacy/tests/regression/test_issue429.py @@ -1,25 +1,25 @@ # coding: utf-8 from __future__ import unicode_literals -from ...attrs import ORTH from ...matcher import Matcher import pytest -@pytest.mark.models +@pytest.mark.models('en') def test_issue429(EN): def merge_phrases(matcher, doc, i, matches): if i != len(matches) - 1: return None - spans = [(ent_id, label, doc[start:end]) for ent_id, label, start, end in matches] + spans = [(ent_id, ent_id, doc[start:end]) for ent_id, start, end in matches] for ent_id, label, span in spans: span.merge('NNP' if label else span.root.tag_, span.text, EN.vocab.strings[label]) doc = EN('a') matcher = Matcher(EN.vocab) - matcher.add('key', label='TEST', attrs={}, specs=[[{ORTH: 'a'}]], on_match=merge_phrases) - doc = EN.tokenizer('a b c') + matcher.add('TEST', merge_phrases, [{'ORTH': 'a'}]) + doc = EN.make_doc('a b c') + EN.tensorizer(doc) EN.tagger(doc) matcher(doc) EN.entity(doc) diff --git a/spacy/tests/regression/test_issue514.py b/spacy/tests/regression/test_issue514.py index a21b7333e..6021efd44 100644 --- a/spacy/tests/regression/test_issue514.py +++ b/spacy/tests/regression/test_issue514.py @@ -6,7 +6,8 @@ from ..util import get_doc import pytest -@pytest.mark.models +@pytest.mark.skip +@pytest.mark.models('en') def test_issue514(EN): """Test serializing after adding entity""" text = ["This", "is", "a", "sentence", "about", "pasta", "."] diff --git a/spacy/tests/regression/test_issue54.py b/spacy/tests/regression/test_issue54.py index 9085457f6..9867a4989 100644 --- a/spacy/tests/regression/test_issue54.py +++ b/spacy/tests/regression/test_issue54.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import pytest -@pytest.mark.models +@pytest.mark.models('en') def test_issue54(EN): text = "Talks given by women had a slightly higher number of questions asked (3.2$\pm$0.2) than talks given by men (2.6$\pm$0.1)." tokens = EN(text) diff --git a/spacy/tests/regression/test_issue587.py b/spacy/tests/regression/test_issue587.py index 1a9620236..fdc23c284 100644 --- a/spacy/tests/regression/test_issue587.py +++ b/spacy/tests/regression/test_issue587.py @@ -7,14 +7,16 @@ from ...attrs import IS_PUNCT, ORTH import pytest -@pytest.mark.models -def test_issue587(EN): +def test_issue587(en_tokenizer): """Test that Matcher doesn't segfault on particular input""" - matcher = Matcher(EN.vocab) - content = '''a b; c''' - matcher.add(entity_key='1', label='TEST', attrs={}, specs=[[{ORTH: 'a'}, {ORTH: 'b'}]]) - matcher(EN(content)) - matcher.add(entity_key='2', label='TEST', attrs={}, specs=[[{ORTH: 'a'}, {ORTH: 'b'}, {IS_PUNCT: True}, {ORTH: 'c'}]]) - matcher(EN(content)) - matcher.add(entity_key='3', label='TEST', attrs={}, specs=[[{ORTH: 'a'}, {ORTH: 'b'}, {IS_PUNCT: True}, {ORTH: 'd'}]]) - matcher(EN(content)) + doc = en_tokenizer('a b; c') + matcher = Matcher(doc.vocab) + matcher.add('TEST1', None, [{ORTH: 'a'}, {ORTH: 'b'}]) + matches = matcher(doc) + assert len(matches) == 1 + matcher.add('TEST2', None, [{ORTH: 'a'}, {ORTH: 'b'}, {IS_PUNCT: True}, {ORTH: 'c'}]) + matches = matcher(doc) + assert len(matches) == 2 + matcher.add('TEST3', None, [{ORTH: 'a'}, {ORTH: 'b'}, {IS_PUNCT: True}, {ORTH: 'd'}]) + matches = matcher(doc) + assert len(matches) == 2 diff --git a/spacy/tests/regression/test_issue588.py b/spacy/tests/regression/test_issue588.py index 1002da226..438f0d161 100644 --- a/spacy/tests/regression/test_issue588.py +++ b/spacy/tests/regression/test_issue588.py @@ -9,4 +9,4 @@ import pytest def test_issue588(en_vocab): matcher = Matcher(en_vocab) with pytest.raises(ValueError): - matcher.add(entity_key='1', label='TEST', attrs={}, specs=[[]]) + matcher.add('TEST', None, []) diff --git a/spacy/tests/regression/test_issue590.py b/spacy/tests/regression/test_issue590.py index 443239cf1..be7c1db48 100644 --- a/spacy/tests/regression/test_issue590.py +++ b/spacy/tests/regression/test_issue590.py @@ -1,7 +1,6 @@ # coding: utf-8 from __future__ import unicode_literals -from ...attrs import ORTH, IS_ALPHA, LIKE_NUM from ...matcher import Matcher from ..util import get_doc @@ -9,14 +8,8 @@ from ..util import get_doc def test_issue590(en_vocab): """Test overlapping matches""" doc = get_doc(en_vocab, ['n', '=', '1', ';', 'a', ':', '5', '%']) - matcher = Matcher(en_vocab) - matcher.add_entity("ab", acceptor=None, on_match=None) - matcher.add_pattern('ab', [{IS_ALPHA: True}, {ORTH: ':'}, - {LIKE_NUM: True}, {ORTH: '%'}], - label='a') - matcher.add_pattern('ab', [{IS_ALPHA: True}, {ORTH: '='}, - {LIKE_NUM: True}], - label='b') + matcher.add('ab', None, [{'IS_ALPHA': True}, {'ORTH': ':'}, {'LIKE_NUM': True}, {'ORTH': '%'}]) + matcher.add('ab', None, [{'IS_ALPHA': True}, {'ORTH': '='}, {'LIKE_NUM': True}]) matches = matcher(doc) assert len(matches) == 2 diff --git a/spacy/tests/regression/test_issue605.py b/spacy/tests/regression/test_issue605.py deleted file mode 100644 index 14b619ebf..000000000 --- a/spacy/tests/regression/test_issue605.py +++ /dev/null @@ -1,21 +0,0 @@ -# coding: utf-8 -from __future__ import unicode_literals - -from ...attrs import ORTH -from ...matcher import Matcher -from ..util import get_doc - - -def test_issue605(en_vocab): - def return_false(doc, ent_id, label, start, end): - return False - - words = ["The", "golf", "club", "is", "broken"] - pattern = [{ORTH: "golf"}, {ORTH: "club"}] - label = "Sport_Equipment" - doc = get_doc(en_vocab, words) - matcher = Matcher(doc.vocab) - matcher.add_entity(label, acceptor=return_false) - matcher.add_pattern(label, pattern) - match = matcher(doc) - assert match == [] diff --git a/spacy/tests/regression/test_issue615.py b/spacy/tests/regression/test_issue615.py index 393b34b34..2e36dae04 100644 --- a/spacy/tests/regression/test_issue615.py +++ b/spacy/tests/regression/test_issue615.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals from ...matcher import Matcher -from ...attrs import ORTH def test_issue615(en_tokenizer): @@ -14,19 +13,19 @@ def test_issue615(en_tokenizer): if i != len(matches)-1: return None # Get Span objects - spans = [(ent_id, label, doc[start : end]) for ent_id, label, start, end in matches] + spans = [(ent_id, ent_id, doc[start : end]) for ent_id, start, end in matches] for ent_id, label, span in spans: - span.merge('NNP' if label else span.root.tag_, span.text, doc.vocab.strings[label]) + span.merge(tag='NNP' if label else span.root.tag_, lemma=span.text, + label=label) + doc.ents = doc.ents + ((label, span.start, span.end),) text = "The golf club is broken" - pattern = [{ORTH: "golf"}, {ORTH: "club"}] + pattern = [{'ORTH': "golf"}, {'ORTH': "club"}] label = "Sport_Equipment" doc = en_tokenizer(text) matcher = Matcher(doc.vocab) - matcher.add_entity(label, on_match=merge_phrases) - matcher.add_pattern(label, pattern, label=label) - + matcher.add(label, merge_phrases, pattern) match = matcher(doc) entities = list(doc.ents) diff --git a/spacy/tests/regression/test_issue617.py b/spacy/tests/regression/test_issue617.py deleted file mode 100644 index f17342565..000000000 --- a/spacy/tests/regression/test_issue617.py +++ /dev/null @@ -1,12 +0,0 @@ -# coding: utf-8 -from __future__ import unicode_literals - -from ...vocab import Vocab - - -def test_issue617(): - """Test loading Vocab with string""" - try: - vocab = Vocab.load('/tmp/vocab') - except IOError: - pass diff --git a/spacy/tests/regression/test_issue686.py b/spacy/tests/regression/test_issue686.py index d3807808a..1323393db 100644 --- a/spacy/tests/regression/test_issue686.py +++ b/spacy/tests/regression/test_issue686.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import pytest -@pytest.mark.models +@pytest.mark.models('en') @pytest.mark.parametrize('text', ["He is the man", "he is the man"]) def test_issue686(EN, text): """Test that pronoun lemmas are assigned correctly.""" diff --git a/spacy/tests/regression/test_issue693.py b/spacy/tests/regression/test_issue693.py index e4d907716..c3541ea91 100644 --- a/spacy/tests/regression/test_issue693.py +++ b/spacy/tests/regression/test_issue693.py @@ -4,7 +4,8 @@ from __future__ import unicode_literals import pytest -@pytest.mark.models +@pytest.mark.xfail +@pytest.mark.models('en') def test_issue693(EN): """Test that doc.noun_chunks parses the complete sentence.""" @@ -14,7 +15,5 @@ def test_issue693(EN): doc2 = EN(text2) chunks1 = [chunk for chunk in doc1.noun_chunks] chunks2 = [chunk for chunk in doc2.noun_chunks] - for word in doc1: - print(word.text, word.dep_, word.head.text) assert len(chunks1) == 2 assert len(chunks2) == 2 diff --git a/spacy/tests/regression/test_issue704.py b/spacy/tests/regression/test_issue704.py index 2cecf6219..51f481a3f 100644 --- a/spacy/tests/regression/test_issue704.py +++ b/spacy/tests/regression/test_issue704.py @@ -4,11 +4,12 @@ from __future__ import unicode_literals import pytest -@pytest.mark.models +@pytest.mark.xfail +@pytest.mark.models('en') def test_issue704(EN): """Test that sentence boundaries are detected correctly.""" text = '“Atticus said to Jem one day, “I’d rather you shot at tin cans in the backyard, but I know you’ll go after birds. Shoot all the blue jays you want, if you can hit ‘em, but remember it’s a sin to kill a mockingbird.”' doc = EN(text) - sents = [sent for sent in doc.sents] + sents = list([sent for sent in doc.sents]) assert len(sents) == 3 diff --git a/spacy/tests/regression/test_issue717.py b/spacy/tests/regression/test_issue717.py index 1548c06aa..69c0705cb 100644 --- a/spacy/tests/regression/test_issue717.py +++ b/spacy/tests/regression/test_issue717.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import pytest -@pytest.mark.models +@pytest.mark.models('en') @pytest.mark.parametrize('text1,text2', [("You're happy", "You are happy"), ("I'm happy", "I am happy"), diff --git a/spacy/tests/regression/test_issue719.py b/spacy/tests/regression/test_issue719.py index 62adbcd44..9b4838bdb 100644 --- a/spacy/tests/regression/test_issue719.py +++ b/spacy/tests/regression/test_issue719.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import pytest -@pytest.mark.models +@pytest.mark.models('en') @pytest.mark.parametrize('text', ["s..."]) def test_issue719(EN, text): """Test that the token 's' is not lemmatized into empty string.""" diff --git a/spacy/tests/regression/test_issue758.py b/spacy/tests/regression/test_issue758.py index a059f095f..48e27be02 100644 --- a/spacy/tests/regression/test_issue758.py +++ b/spacy/tests/regression/test_issue758.py @@ -1,16 +1,13 @@ from __future__ import unicode_literals -from ... import load as load_spacy -from ...attrs import LEMMA -from ...matcher import merge_phrase import pytest -@pytest.mark.models -def test_issue758(): +@pytest.mark.xfail +@pytest.mark.models('en') +def test_issue758(EN): '''Test parser transition bug after label added.''' - nlp = load_spacy('en') - nlp.matcher.add('splash', 'my_entity', {}, - [[{LEMMA: 'splash'}, {LEMMA: 'on'}]], - on_match=merge_phrase) + from ...matcher import merge_phrase + nlp = EN() + nlp.matcher.add('splash', merge_phrase, [[{'LEMMA': 'splash'}, {'LEMMA': 'on'}]]) doc = nlp('splash On', parse=False) diff --git a/spacy/tests/regression/test_issue768.py b/spacy/tests/regression/test_issue768.py index a5feb447d..b98610ac7 100644 --- a/spacy/tests/regression/test_issue768.py +++ b/spacy/tests/regression/test_issue768.py @@ -30,6 +30,7 @@ def fr_tokenizer_w_infix(): return French.Defaults.create_tokenizer() +@pytest.mark.skip @pytest.mark.parametrize('text,expected_tokens', [("l'avion", ["l'", "avion"]), ("j'ai", ["j'", "ai"])]) def test_issue768(fr_tokenizer_w_infix, text, expected_tokens): diff --git a/spacy/tests/regression/test_issue781.py b/spacy/tests/regression/test_issue781.py index 1c48d1534..e3f391a37 100644 --- a/spacy/tests/regression/test_issue781.py +++ b/spacy/tests/regression/test_issue781.py @@ -5,6 +5,8 @@ import pytest # Note: "chromosomes" worked previous the bug fix +@pytest.mark.models('en') @pytest.mark.parametrize('word,lemmas', [("chromosomes", ["chromosome"]), ("endosomes", ["endosome"]), ("colocalizes", ["colocalize", "colocaliz"])]) -def test_issue781(lemmatizer, word, lemmas): +def test_issue781(EN, word, lemmas): + lemmatizer = EN.Defaults.create_lemmatizer() assert lemmatizer(word, 'noun', morphology={'number': 'plur'}) == set(lemmas) diff --git a/spacy/tests/regression/test_issue834.py b/spacy/tests/regression/test_issue834.py index 7cb63a77d..d3dee49e8 100644 --- a/spacy/tests/regression/test_issue834.py +++ b/spacy/tests/regression/test_issue834.py @@ -1,5 +1,6 @@ # coding: utf-8 from __future__ import unicode_literals +import pytest word2vec_str = """, -0.046107 -0.035951 -0.560418 @@ -8,6 +9,7 @@ de -0.648927 -0.400976 -0.527124 \u00A0 -1.499184 -0.184280 -0.598371""" +@pytest.mark.xfail def test_issue834(en_vocab, text_file): """Test that no-break space (U+00A0) is detected as space by the load_vectors function.""" text_file.write(word2vec_str) diff --git a/spacy/tests/regression/test_issue850.py b/spacy/tests/regression/test_issue850.py index 8237763ea..01bc19fb9 100644 --- a/spacy/tests/regression/test_issue850.py +++ b/spacy/tests/regression/test_issue850.py @@ -1,8 +1,5 @@ -''' -Test Matcher matches with '*' operator and Boolean flag -''' +# coding: utf-8 from __future__ import unicode_literals -from __future__ import print_function import pytest from ...matcher import Matcher @@ -12,41 +9,30 @@ from ...tokens import Doc def test_basic_case(): + """Test Matcher matches with '*' operator and Boolean flag""" matcher = Matcher(Vocab( lex_attr_getters={LOWER: lambda string: string.lower()})) IS_ANY_TOKEN = matcher.vocab.add_flag(lambda x: True) - matcher.add_pattern( - "FarAway", - [ - {LOWER: "bob"}, - {'OP': '*', LOWER: 'and'}, - {LOWER: 'frank'} - ]) + matcher.add('FarAway', None, [{'LOWER': "bob"}, {'OP': '*', 'LOWER': 'and'}, {'LOWER': 'frank'}]) doc = Doc(matcher.vocab, words=['bob', 'and', 'and', 'frank']) match = matcher(doc) assert len(match) == 1 - ent_id, label, start, end = match[0] + ent_id, start, end = match[0] assert start == 0 assert end == 4 @pytest.mark.xfail def test_issue850(): - '''The problem here is that the variable-length pattern matches the - succeeding token. We then don't handle the ambiguity correctly.''' + """The problem here is that the variable-length pattern matches the + succeeding token. We then don't handle the ambiguity correctly.""" matcher = Matcher(Vocab( lex_attr_getters={LOWER: lambda string: string.lower()})) IS_ANY_TOKEN = matcher.vocab.add_flag(lambda x: True) - matcher.add_pattern( - "FarAway", - [ - {LOWER: "bob"}, - {'OP': '*', IS_ANY_TOKEN: True}, - {LOWER: 'frank'} - ]) + matcher.add('FarAway', None, [{'LOWER': "bob"}, {'OP': '*', 'IS_ANY_TOKEN': True}, {'LOWER': 'frank'}]) doc = Doc(matcher.vocab, words=['bob', 'and', 'and', 'frank']) match = matcher(doc) assert len(match) == 1 - ent_id, label, start, end = match[0] + ent_id, start, end = match[0] assert start == 0 assert end == 4 diff --git a/spacy/tests/regression/test_issue910.py b/spacy/tests/regression/test_issue910.py index 4505b500e..b35ce94bc 100644 --- a/spacy/tests/regression/test_issue910.py +++ b/spacy/tests/regression/test_issue910.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals import json -import os import random import contextlib import shutil @@ -9,7 +8,6 @@ import tempfile from pathlib import Path -import pathlib from ...gold import GoldParse from ...pipeline import EntityRecognizer from ...lang.en import English @@ -57,21 +55,15 @@ def additional_entity_types(): @contextlib.contextmanager def temp_save_model(model): - model_dir = Path(tempfile.mkdtemp()) - # store the fine tuned model - with (model_dir / "config.json").open('w') as file_: - data = json.dumps(model.cfg) - if not isinstance(data, unicode): - data = data.decode('utf8') - file_.write(data) - model.model.dump((model_dir / 'model').as_posix()) + model_dir = tempfile.mkdtemp() + model.to_disk(model_dir) yield model_dir shutil.rmtree(model_dir.as_posix()) - -@pytest.mark.models -def test_issue910(train_data, additional_entity_types): +@pytest.mark.xfail +@pytest.mark.models('en') +def test_issue910(EN, train_data, additional_entity_types): '''Test that adding entities and resuming training works passably OK. There are two issues here: @@ -79,25 +71,27 @@ def test_issue910(train_data, additional_entity_types): 2) There's no way to set the learning rate for the weight update, so we end up out-of-scale, causing it to learn too fast. ''' - nlp = English() + nlp = EN doc = nlp(u"I am looking for a restaurant in Berlin") ents_before_train = [(ent.label_, ent.text) for ent in doc.ents] # Fine tune the ner model for entity_type in additional_entity_types: nlp.entity.add_label(entity_type) - nlp.entity.model.learn_rate = 0.001 + sgd = Adam(nlp.entity.model[0].ops, 0.001) for itn in range(10): random.shuffle(train_data) for raw_text, entity_offsets in train_data: doc = nlp.make_doc(raw_text) nlp.tagger(doc) + nlp.tensorizer(doc) gold = GoldParse(doc, entities=entity_offsets) - loss = nlp.entity.update(doc, gold) + loss = nlp.entity.update(doc, gold, sgd=sgd, drop=0.5) with temp_save_model(nlp.entity) as model_dir: # Load the fine tuned model - loaded_ner = EntityRecognizer.load(model_dir, nlp.vocab) + loaded_ner = EntityRecognizer(nlp.vocab) + loaded_ner.from_disk(model_dir) for raw_text, entity_offsets in train_data: doc = nlp.make_doc(raw_text) @@ -105,6 +99,4 @@ def test_issue910(train_data, additional_entity_types): loaded_ner(doc) ents = {(ent.start_char, ent.end_char): ent.label_ for ent in doc.ents} for start, end, label in entity_offsets: - if (start, end) not in ents: - print(ents) assert ents[(start, end)] == label diff --git a/spacy/tests/regression/test_issue995.py b/spacy/tests/regression/test_issue995.py index 633e96fb5..420185bab 100644 --- a/spacy/tests/regression/test_issue995.py +++ b/spacy/tests/regression/test_issue995.py @@ -1,21 +1,15 @@ from __future__ import unicode_literals import pytest -from ... import load as load_spacy - -@pytest.fixture -def doc(): - nlp = load_spacy('en') - return nlp('Does flight number three fifty-four require a connecting flight' - ' to get to Boston?') -@pytest.mark.models -def test_issue955(doc): +@pytest.mark.models('en') +def test_issue955(EN): '''Test that we don't have any nested noun chunks''' + doc = EN('Does flight number three fifty-four require a connecting flight' + ' to get to Boston?') seen_tokens = set() for np in doc.noun_chunks: - print(np.text, np.root.text, np.root.dep_, np.root.tag_) for word in np: key = (word.i, word.text) assert key not in seen_tokens diff --git a/spacy/tests/serialize/test_serialization.py b/spacy/tests/serialize/test_serialization.py index 52c42b94d..036035095 100644 --- a/spacy/tests/serialize/test_serialization.py +++ b/spacy/tests/serialize/test_serialization.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals from ..util import get_doc, assert_docs_equal from ...tokens import Doc +from ...vocab import Vocab import pytest @@ -22,6 +23,15 @@ def test_serialize_empty_doc(en_vocab): for token1, token2 in zip(doc, doc2): assert token1.text == token2.text + +@pytest.mark.xfail +@pytest.mark.parametrize('text', ['rat']) +def test_serialize_vocab(en_vocab, text): + text_hash = en_vocab.strings.add(text) + vocab_bytes = en_vocab.to_bytes() + new_vocab = Vocab().from_bytes(vocab_bytes) + assert new_vocab.strings(text_hash) == text + # #@pytest.mark.parametrize('text', [TEXT]) #def test_serialize_tokens(en_vocab, text): diff --git a/spacy/tests/serialize/test_serialize_parser_ner.py b/spacy/tests/serialize/test_serialize_parser_ner.py new file mode 100644 index 000000000..ae9e23e9a --- /dev/null +++ b/spacy/tests/serialize/test_serialize_parser_ner.py @@ -0,0 +1,34 @@ +# coding: utf-8 +from __future__ import unicode_literals + +from ..util import make_tempdir +from ...pipeline import NeuralDependencyParser as DependencyParser +from ...pipeline import NeuralEntityRecognizer as EntityRecognizer + +import pytest + + +test_parsers = [DependencyParser, EntityRecognizer] + + +@pytest.mark.parametrize('Parser', test_parsers) +def test_serialize_parser_roundtrip_bytes(en_vocab, Parser): + parser = Parser(en_vocab) + parser.model, _ = parser.Model(10) + new_parser = Parser(en_vocab) + new_parser.model, _ = new_parser.Model(10) + new_parser = new_parser.from_bytes(parser.to_bytes()) + assert new_parser.to_bytes() == parser.to_bytes() + + +@pytest.mark.parametrize('Parser', test_parsers) +def test_serialize_parser_roundtrip_disk(en_vocab, Parser): + parser = Parser(en_vocab) + parser.model, _ = parser.Model(0) + with make_tempdir() as d: + file_path = d / 'parser' + parser.to_disk(file_path) + parser_d = Parser(en_vocab) + parser_d.model, _ = parser_d.Model(0) + parser_d = parser_d.from_disk(file_path) + assert parser.to_bytes(model=False) == parser_d.to_bytes(model=False) diff --git a/spacy/tests/serialize/test_serialize_stringstore.py b/spacy/tests/serialize/test_serialize_stringstore.py new file mode 100644 index 000000000..594413922 --- /dev/null +++ b/spacy/tests/serialize/test_serialize_stringstore.py @@ -0,0 +1,46 @@ +# coding: utf-8 +from __future__ import unicode_literals + +from ..util import make_tempdir +from ...strings import StringStore + +import pytest + + +test_strings = [([], []), (['rats', 'are', 'cute'], ['i', 'like', 'rats'])] + + +@pytest.mark.parametrize('strings1,strings2', test_strings) +def test_serialize_stringstore_roundtrip_bytes(strings1,strings2): + sstore1 = StringStore(strings=strings1) + sstore2 = StringStore(strings=strings2) + sstore1_b = sstore1.to_bytes() + sstore2_b = sstore2.to_bytes() + if strings1 == strings2: + assert sstore1_b == sstore2_b + else: + assert sstore1_b != sstore2_b + sstore1 = sstore1.from_bytes(sstore1_b) + assert sstore1.to_bytes() == sstore1_b + new_sstore1 = StringStore().from_bytes(sstore1_b) + assert new_sstore1.to_bytes() == sstore1_b + assert list(new_sstore1) == strings1 + + +@pytest.mark.parametrize('strings1,strings2', test_strings) +def test_serialize_stringstore_roundtrip_disk(strings1,strings2): + sstore1 = StringStore(strings=strings1) + sstore2 = StringStore(strings=strings2) + with make_tempdir() as d: + file_path1 = d / 'strings1' + file_path2 = d / 'strings2' + sstore1.to_disk(file_path1) + sstore2.to_disk(file_path2) + sstore1_d = StringStore().from_disk(file_path1) + sstore2_d = StringStore().from_disk(file_path2) + assert list(sstore1_d) == list(sstore1) + assert list(sstore2_d) == list(sstore2) + if strings1 == strings2: + assert list(sstore1_d) == list(sstore2_d) + else: + assert list(sstore1_d) != list(sstore2_d) diff --git a/spacy/tests/serialize/test_serialize_tagger.py b/spacy/tests/serialize/test_serialize_tagger.py new file mode 100644 index 000000000..fa9a776bb --- /dev/null +++ b/spacy/tests/serialize/test_serialize_tagger.py @@ -0,0 +1,39 @@ +# coding: utf-8 +from __future__ import unicode_literals + +from ..util import make_tempdir +from ...pipeline import NeuralTagger as Tagger + +import pytest + + +@pytest.fixture +def taggers(en_vocab): + tagger1 = Tagger(en_vocab) + tagger2 = Tagger(en_vocab) + tagger1.model = tagger1.Model(None, None) + tagger2.model = tagger2.Model(None, None) + return (tagger1, tagger2) + + +def test_serialize_tagger_roundtrip_bytes(en_vocab, taggers): + tagger1, tagger2 = taggers + tagger1_b = tagger1.to_bytes() + tagger2_b = tagger2.to_bytes() + assert tagger1_b == tagger2_b + tagger1 = tagger1.from_bytes(tagger1_b) + assert tagger1.to_bytes() == tagger1_b + new_tagger1 = Tagger(en_vocab).from_bytes(tagger1_b) + assert new_tagger1.to_bytes() == tagger1_b + + +def test_serialize_tagger_roundtrip_disk(en_vocab, taggers): + tagger1, tagger2 = taggers + with make_tempdir() as d: + file_path1 = d / 'tagger1' + file_path2 = d / 'tagger2' + tagger1.to_disk(file_path1) + tagger2.to_disk(file_path2) + tagger1_d = Tagger(en_vocab).from_disk(file_path1) + tagger2_d = Tagger(en_vocab).from_disk(file_path2) + assert tagger1_d.to_bytes() == tagger2_d.to_bytes() diff --git a/spacy/tests/serialize/test_serialize_tensorizer.py b/spacy/tests/serialize/test_serialize_tensorizer.py new file mode 100644 index 000000000..ba01a2fa6 --- /dev/null +++ b/spacy/tests/serialize/test_serialize_tensorizer.py @@ -0,0 +1,25 @@ +# coding: utf-8 +from __future__ import unicode_literals + +from ..util import make_tempdir +from ...pipeline import TokenVectorEncoder as Tensorizer + +import pytest + + +def test_serialize_tensorizer_roundtrip_bytes(en_vocab): + tensorizer = Tensorizer(en_vocab) + tensorizer.model = tensorizer.Model() + tensorizer_b = tensorizer.to_bytes() + new_tensorizer = Tensorizer(en_vocab).from_bytes(tensorizer_b) + assert new_tensorizer.to_bytes() == tensorizer_b + + +def test_serialize_tensorizer_roundtrip_disk(en_vocab): + tensorizer = Tensorizer(en_vocab) + tensorizer.model = tensorizer.Model() + with make_tempdir() as d: + file_path = d / 'tensorizer' + tensorizer.to_disk(file_path) + tensorizer_d = Tensorizer(en_vocab).from_disk(file_path) + assert tensorizer.to_bytes() == tensorizer_d.to_bytes() diff --git a/spacy/tests/serialize/test_serialize_tokenizer.py b/spacy/tests/serialize/test_serialize_tokenizer.py new file mode 100644 index 000000000..d002c1b75 --- /dev/null +++ b/spacy/tests/serialize/test_serialize_tokenizer.py @@ -0,0 +1,35 @@ +# coding: utf-8 +from __future__ import unicode_literals + +from ...util import get_lang_class +from ..util import make_tempdir, assert_packed_msg_equal + +import pytest + + +def load_tokenizer(b): + tok = get_lang_class('en').Defaults.create_tokenizer() + tok.from_bytes(b) + return tok + + +@pytest.mark.xfail +@pytest.mark.parametrize('text', ["I💜you", "they’re", "“hello”"]) +def test_serialize_tokenizer_roundtrip_bytes(en_tokenizer, text): + tokenizer = en_tokenizer + new_tokenizer = load_tokenizer(tokenizer.to_bytes()) + assert_packed_msg_equal(new_tokenizer.to_bytes(), tokenizer.to_bytes()) + # assert new_tokenizer.to_bytes() == tokenizer.to_bytes() + doc1 = tokenizer(text) + doc2 = new_tokenizer(text) + assert [token.text for token in doc1] == [token.text for token in doc2] + + +@pytest.mark.xfail +def test_serialize_tokenizer_roundtrip_disk(en_tokenizer): + tokenizer = en_tokenizer + with make_tempdir() as d: + file_path = d / 'tokenizer' + tokenizer.to_disk(file_path) + tokenizer_d = en_tokenizer.from_disk(file_path) + assert tokenizer.to_bytes() == tokenizer_d.to_bytes() diff --git a/spacy/tests/serialize/test_serialize_vocab.py b/spacy/tests/serialize/test_serialize_vocab.py new file mode 100644 index 000000000..47749e69f --- /dev/null +++ b/spacy/tests/serialize/test_serialize_vocab.py @@ -0,0 +1,73 @@ +# coding: utf-8 +from __future__ import unicode_literals + +from ..util import make_tempdir +from ...vocab import Vocab + +import pytest + + +test_strings = [([], []), (['rats', 'are', 'cute'], ['i', 'like', 'rats'])] +test_strings_attrs = [(['rats', 'are', 'cute'], 'Hello')] + + +@pytest.mark.parametrize('strings1,strings2', test_strings) +def test_serialize_vocab_roundtrip_bytes(strings1,strings2): + vocab1 = Vocab(strings=strings1) + vocab2 = Vocab(strings=strings2) + vocab1_b = vocab1.to_bytes() + vocab2_b = vocab2.to_bytes() + if strings1 == strings2: + assert vocab1_b == vocab2_b + else: + assert vocab1_b != vocab2_b + vocab1 = vocab1.from_bytes(vocab1_b) + assert vocab1.to_bytes() == vocab1_b + new_vocab1 = Vocab().from_bytes(vocab1_b) + assert new_vocab1.to_bytes() == vocab1_b + assert len(new_vocab1) == len(strings1) + assert sorted([lex.text for lex in new_vocab1]) == sorted(strings1) + + +@pytest.mark.parametrize('strings1,strings2', test_strings) +def test_serialize_vocab_roundtrip_disk(strings1,strings2): + vocab1 = Vocab(strings=strings1) + vocab2 = Vocab(strings=strings2) + with make_tempdir() as d: + file_path1 = d / 'vocab1' + file_path2 = d / 'vocab2' + vocab1.to_disk(file_path1) + vocab2.to_disk(file_path2) + vocab1_d = Vocab().from_disk(file_path1) + vocab2_d = Vocab().from_disk(file_path2) + assert list(vocab1_d) == list(vocab1) + assert list(vocab2_d) == list(vocab2) + if strings1 == strings2: + assert list(vocab1_d) == list(vocab2_d) + else: + assert list(vocab1_d) != list(vocab2_d) + + +@pytest.mark.parametrize('strings,lex_attr', test_strings_attrs) +def test_serialize_vocab_lex_attrs_bytes(strings, lex_attr): + vocab1 = Vocab(strings=strings) + vocab2 = Vocab() + vocab1[strings[0]].norm_ = lex_attr + assert vocab1[strings[0]].norm_ == lex_attr + assert vocab2[strings[0]].norm_ != lex_attr + vocab2 = vocab2.from_bytes(vocab1.to_bytes()) + assert vocab2[strings[0]].norm_ == lex_attr + + +@pytest.mark.parametrize('strings,lex_attr', test_strings_attrs) +def test_serialize_vocab_lex_attrs_disk(strings, lex_attr): + vocab1 = Vocab(strings=strings) + vocab2 = Vocab() + vocab1[strings[0]].norm_ = lex_attr + assert vocab1[strings[0]].norm_ == lex_attr + assert vocab2[strings[0]].norm_ != lex_attr + with make_tempdir() as d: + file_path = d / 'vocab' + vocab1.to_disk(file_path) + vocab2 = vocab2.from_disk(file_path) + assert vocab2[strings[0]].norm_ == lex_attr diff --git a/spacy/tests/stringstore/test_freeze_string_store.py b/spacy/tests/stringstore/test_freeze_string_store.py index 96d7912b2..ebfddccac 100644 --- a/spacy/tests/stringstore/test_freeze_string_store.py +++ b/spacy/tests/stringstore/test_freeze_string_store.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals import pytest +@pytest.mark.xfail @pytest.mark.parametrize('text', [["a", "b", "c"]]) def test_stringstore_freeze_oov(stringstore, text): assert stringstore[text[0]] == 1 diff --git a/spacy/tests/stringstore/test_stringstore.py b/spacy/tests/stringstore/test_stringstore.py index ebbec01d9..65b994606 100644 --- a/spacy/tests/stringstore/test_stringstore.py +++ b/spacy/tests/stringstore/test_stringstore.py @@ -6,73 +6,86 @@ from ...strings import StringStore import pytest +def test_stringstore_from_api_docs(stringstore): + apple_hash = stringstore.add('apple') + assert apple_hash == 8566208034543834098 + assert stringstore[apple_hash] == u'apple' + + assert u'apple' in stringstore + assert u'cherry' not in stringstore + + orange_hash = stringstore.add('orange') + all_strings = [s for s in stringstore] + assert all_strings == [u'apple', u'orange'] + + banana_hash = stringstore.add('banana') + assert len(stringstore) == 3 + assert banana_hash == 2525716904149915114 + assert stringstore[banana_hash] == u'banana' + assert stringstore[u'banana'] == banana_hash + + @pytest.mark.parametrize('text1,text2,text3', [(b'Hello', b'goodbye', b'hello')]) def test_stringstore_save_bytes(stringstore, text1, text2, text3): - i = stringstore[text1] - assert i == 1 - assert stringstore[text1] == 1 - assert stringstore[text2] != i - assert stringstore[text3] != i - assert i == 1 + key = stringstore.add(text1) + assert stringstore[text1] == key + assert stringstore[text2] != key + assert stringstore[text3] != key @pytest.mark.parametrize('text1,text2,text3', [('Hello', 'goodbye', 'hello')]) def test_stringstore_save_unicode(stringstore, text1, text2, text3): - i = stringstore[text1] - assert i == 1 - assert stringstore[text1] == 1 - assert stringstore[text2] != i - assert stringstore[text3] != i - assert i == 1 + key = stringstore.add(text1) + assert stringstore[text1] == key + assert stringstore[text2] != key + assert stringstore[text3] != key @pytest.mark.parametrize('text', [b'A']) def test_stringstore_retrieve_id(stringstore, text): - i = stringstore[text] - assert stringstore.size == 1 - assert stringstore[1] == text.decode('utf8') - with pytest.raises(IndexError): - stringstore[2] + key = stringstore.add(text) + assert len(stringstore) == 1 + assert stringstore[key] == text.decode('utf8') + with pytest.raises(KeyError): + stringstore[20000] @pytest.mark.parametrize('text1,text2', [(b'0123456789', b'A')]) def test_stringstore_med_string(stringstore, text1, text2): - store = stringstore[text1] + store = stringstore.add(text1) assert stringstore[store] == text1.decode('utf8') - dummy = stringstore[text2] + dummy = stringstore.add(text2) assert stringstore[text1] == store def test_stringstore_long_string(stringstore): text = "INFORMATIVE](http://www.google.com/search?as_q=RedditMonkey&hl=en&num=50&btnG=Google+Search&as_epq=&as_oq=&as_eq=&lr=&as_ft=i&as_filetype=&as_qdr=all&as_nlo=&as_nhi=&as_occt=any&as_dt=i&as_sitesearch=&as_rights=&safe=off" - store = stringstore[text] + store = stringstore.add(text) assert stringstore[store] == text @pytest.mark.parametrize('factor', [254, 255, 256]) def test_stringstore_multiply(stringstore, factor): text = 'a' * factor - store = stringstore[text] + store = stringstore.add(text) assert stringstore[store] == text def test_stringstore_massive_strings(stringstore): text = 'a' * 511 - store = stringstore[text] + store = stringstore.add(text) assert stringstore[store] == text text2 = 'z' * 512 - store = stringstore[text2] + store = stringstore.add(text2) assert stringstore[store] == text2 text3 = '1' * 513 - store = stringstore[text3] + store = stringstore.add(text3) assert stringstore[store] == text3 @pytest.mark.parametrize('text', ["qqqqq"]) -def test_stringstore_dump_load(stringstore, text_file, text): - store = stringstore[text] - stringstore.dump(text_file) - text_file.seek(0) - new_stringstore = StringStore() - new_stringstore.load(text_file) +def test_stringstore_to_bytes(stringstore, text): + store = stringstore.add(text) + serialized = stringstore.to_bytes() + new_stringstore = StringStore().from_bytes(serialized) assert new_stringstore[store] == text diff --git a/spacy/tests/tagger/__init__.py b/spacy/tests/tagger/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/spacy/tests/tagger/test_lemmatizer.py b/spacy/tests/tagger/test_lemmatizer.py deleted file mode 100644 index 5db0d0b2c..000000000 --- a/spacy/tests/tagger/test_lemmatizer.py +++ /dev/null @@ -1,49 +0,0 @@ -# coding: utf-8 -from __future__ import unicode_literals - -import pytest - - -@pytest.mark.models -@pytest.mark.parametrize('text,lemmas', [("aardwolves", ["aardwolf"]), - ("aardwolf", ["aardwolf"]), - ("planets", ["planet"]), - ("ring", ["ring"]), - ("axes", ["axis", "axe", "ax"])]) -def test_tagger_lemmatizer_noun_lemmas(lemmatizer, text, lemmas): - if lemmatizer is None: - return None - assert lemmatizer.noun(text) == set(lemmas) - - -@pytest.mark.xfail -@pytest.mark.models -def test_tagger_lemmatizer_base_forms(lemmatizer): - if lemmatizer is None: - return None - assert lemmatizer.noun('dive', {'number': 'sing'}) == set(['dive']) - assert lemmatizer.noun('dive', {'number': 'plur'}) == set(['diva']) - - -@pytest.mark.models -def test_tagger_lemmatizer_base_form_verb(lemmatizer): - if lemmatizer is None: - return None - assert lemmatizer.verb('saw', {'verbform': 'past'}) == set(['see']) - - -@pytest.mark.models -def test_tagger_lemmatizer_punct(lemmatizer): - if lemmatizer is None: - return None - assert lemmatizer.punct('“') == set(['"']) - assert lemmatizer.punct('“') == set(['"']) - - -@pytest.mark.models -def test_tagger_lemmatizer_lemma_assignment(EN): - text = "Bananas in pyjamas are geese." - doc = EN.tokenizer(text) - assert all(t.lemma_ == '' for t in doc) - EN.tagger(doc) - assert all(t.lemma_ != '' for t in doc) diff --git a/spacy/tests/tagger/test_morph_exceptions.py b/spacy/tests/tagger/test_morph_exceptions.py deleted file mode 100644 index 63b0a9c15..000000000 --- a/spacy/tests/tagger/test_morph_exceptions.py +++ /dev/null @@ -1,17 +0,0 @@ -# coding: utf-8 -from __future__ import unicode_literals - -from ..util import get_doc - -import pytest - - -def test_tagger_load_morph_exc(en_tokenizer): - text = "I like his style." - tags = ['PRP', 'VBP', 'PRP$', 'NN', '.'] - morph_exc = {'VBP': {'like': {'L': 'luck'}}} - en_tokenizer.vocab.morphology.load_morph_exceptions(morph_exc) - tokens = en_tokenizer(text) - doc = get_doc(tokens.vocab, [t.text for t in tokens], tags=tags) - assert doc[1].tag_ == 'VBP' - assert doc[1].lemma_ == 'luck' diff --git a/spacy/tests/tagger/test_spaces.py b/spacy/tests/tagger/test_spaces.py deleted file mode 100644 index 5b12eba7f..000000000 --- a/spacy/tests/tagger/test_spaces.py +++ /dev/null @@ -1,35 +0,0 @@ -# coding: utf-8 -"""Ensure spaces are assigned the POS tag SPACE""" - - -from __future__ import unicode_literals -from ...parts_of_speech import SPACE - -import pytest - - -@pytest.mark.models -def test_tagger_spaces(EN): - text = "Some\nspaces are\tnecessary." - doc = EN(text, tag=True, parse=False) - assert doc[0].pos != SPACE - assert doc[0].pos_ != 'SPACE' - assert doc[1].pos == SPACE - assert doc[1].pos_ == 'SPACE' - assert doc[1].tag_ == 'SP' - assert doc[2].pos != SPACE - assert doc[3].pos != SPACE - assert doc[4].pos == SPACE - - -@pytest.mark.models -def test_tagger_return_char(EN): - text = ('hi Aaron,\r\n\r\nHow is your schedule today, I was wondering if ' - 'you had time for a phone\r\ncall this afternoon?\r\n\r\n\r\n') - tokens = EN(text) - for token in tokens: - if token.is_space: - assert token.pos == SPACE - assert tokens[3].text == '\r\n\r\n' - assert tokens[3].is_space - assert tokens[3].pos == SPACE diff --git a/spacy/tests/tagger/test_tag_names.py b/spacy/tests/tagger/test_tag_names.py deleted file mode 100644 index 9c5b0adcc..000000000 --- a/spacy/tests/tagger/test_tag_names.py +++ /dev/null @@ -1,16 +0,0 @@ -# coding: utf-8 -from __future__ import unicode_literals - -import six -import pytest - - -@pytest.mark.models -def test_tag_names(EN): - text = "I ate pizzas with anchovies." - doc = EN(text, parse=False, tag=True) - assert type(doc[2].pos) == int - assert isinstance(doc[2].pos_, six.text_type) - assert type(doc[2].dep) == int - assert isinstance(doc[2].dep_, six.text_type) - assert doc[2].tag_ == u'NNS' diff --git a/spacy/tests/matcher/test_matcher.py b/spacy/tests/test_matcher.py similarity index 52% rename from spacy/tests/matcher/test_matcher.py rename to spacy/tests/test_matcher.py index 7c9c4ddfe..388aab03e 100644 --- a/spacy/tests/matcher/test_matcher.py +++ b/spacy/tests/test_matcher.py @@ -1,27 +1,65 @@ # coding: utf-8 from __future__ import unicode_literals -from ...matcher import Matcher, PhraseMatcher -from ..util import get_doc +from ..matcher import Matcher, PhraseMatcher +from .util import get_doc import pytest @pytest.fixture def matcher(en_vocab): - patterns = { - 'JS': ['PRODUCT', {}, [[{'ORTH': 'JavaScript'}]]], - 'GoogleNow': ['PRODUCT', {}, [[{'ORTH': 'Google'}, {'ORTH': 'Now'}]]], - 'Java': ['PRODUCT', {}, [[{'LOWER': 'java'}]]] + rules = { + 'JS': [[{'ORTH': 'JavaScript'}]], + 'GoogleNow': [[{'ORTH': 'Google'}, {'ORTH': 'Now'}]], + 'Java': [[{'LOWER': 'java'}]] } - return Matcher(en_vocab, patterns) + matcher = Matcher(en_vocab) + for key, patterns in rules.items(): + matcher.add(key, None, *patterns) + return matcher + + +def test_matcher_from_api_docs(en_vocab): + matcher = Matcher(en_vocab) + pattern = [{'ORTH': 'test'}] + assert len(matcher) == 0 + matcher.add('Rule', None, pattern) + assert len(matcher) == 1 + matcher.remove('Rule') + assert 'Rule' not in matcher + matcher.add('Rule', None, pattern) + assert 'Rule' in matcher + on_match, patterns = matcher.get('Rule') + assert len(patterns[0]) + + +@pytest.mark.xfail +def test_matcher_from_usage_docs(en_vocab): + text = "Wow 😀 This is really cool! 😂 😂" + doc = get_doc(en_vocab, words=text.split(' ')) + pos_emoji = [u'😀', u'😃', u'😂', u'🤣', u'😊', u'😍'] + pos_patterns = [[{'ORTH': emoji}] for emoji in pos_emoji] + + def label_sentiment(matcher, doc, i, matches): + match_id, start, end = matches[i] + if doc.vocab.strings[match_id] == 'HAPPY': + doc.sentiment += 0.1 + span = doc[start : end] + token = span.merge(norm='happy emoji') + + matcher = Matcher(en_vocab) + matcher.add('HAPPY', label_sentiment, *pos_patterns) + matches = matcher(doc) + assert doc.sentiment != 0 + assert doc[1].norm_ == 'happy emoji' @pytest.mark.parametrize('words', [["Some", "words"]]) def test_matcher_init(en_vocab, words): matcher = Matcher(en_vocab) doc = get_doc(en_vocab, words) - assert matcher.n_patterns == 0 + assert len(matcher) == 0 assert matcher(doc) == [] @@ -32,39 +70,35 @@ def test_matcher_no_match(matcher): def test_matcher_compile(matcher): - assert matcher.n_patterns == 3 + assert len(matcher) == 3 def test_matcher_match_start(matcher): words = ["JavaScript", "is", "good"] doc = get_doc(matcher.vocab, words) - assert matcher(doc) == [(matcher.vocab.strings['JS'], - matcher.vocab.strings['PRODUCT'], 0, 1)] + assert matcher(doc) == [(matcher.vocab.strings['JS'], 0, 1)] def test_matcher_match_end(matcher): words = ["I", "like", "java"] doc = get_doc(matcher.vocab, words) - assert matcher(doc) == [(doc.vocab.strings['Java'], - doc.vocab.strings['PRODUCT'], 2, 3)] + assert matcher(doc) == [(doc.vocab.strings['Java'], 2, 3)] def test_matcher_match_middle(matcher): words = ["I", "like", "Google", "Now", "best"] doc = get_doc(matcher.vocab, words) - assert matcher(doc) == [(doc.vocab.strings['GoogleNow'], - doc.vocab.strings['PRODUCT'], 2, 4)] + assert matcher(doc) == [(doc.vocab.strings['GoogleNow'], 2, 4)] def test_matcher_match_multi(matcher): words = ["I", "like", "Google", "Now", "and", "java", "best"] doc = get_doc(matcher.vocab, words) - assert matcher(doc) == [(doc.vocab.strings['GoogleNow'], - doc.vocab.strings['PRODUCT'], 2, 4), - (doc.vocab.strings['Java'], - doc.vocab.strings['PRODUCT'], 5, 6)] + assert matcher(doc) == [(doc.vocab.strings['GoogleNow'], 2, 4), + (doc.vocab.strings['Java'], 5, 6)] +@pytest.mark.xfail def test_matcher_phrase_matcher(en_vocab): words = ["Google", "Now"] doc = get_doc(en_vocab, words) @@ -87,13 +121,13 @@ def test_matcher_match_zero(matcher): {'IS_PUNCT': True}, {'ORTH': '"'}] - matcher.add('Quote', '', {}, [pattern1]) + matcher.add('Quote', None, pattern1) doc = get_doc(matcher.vocab, words1) assert len(matcher(doc)) == 1 doc = get_doc(matcher.vocab, words2) assert len(matcher(doc)) == 0 - matcher.add('Quote', '', {}, [pattern2]) + matcher.add('Quote', None, pattern2) assert len(matcher(doc)) == 0 @@ -102,24 +136,18 @@ def test_matcher_match_zero_plus(matcher): pattern = [{'ORTH': '"'}, {'OP': '*', 'IS_PUNCT': False}, {'ORTH': '"'}] - matcher.add('Quote', '', {}, [pattern]) + matcher.add('Quote', None, pattern) doc = get_doc(matcher.vocab, words) assert len(matcher(doc)) == 1 + def test_matcher_match_one_plus(matcher): control = Matcher(matcher.vocab) - control.add_pattern('BasicPhilippe', - [{'ORTH': 'Philippe'}], label=321) - + control.add('BasicPhilippe', None, [{'ORTH': 'Philippe'}]) doc = get_doc(control.vocab, ['Philippe', 'Philippe']) - m = control(doc) assert len(m) == 2 - matcher.add_pattern('KleenePhilippe', - [ - {'ORTH': 'Philippe', 'OP': '1'}, - {'ORTH': 'Philippe', 'OP': '+'}], label=321) + matcher.add('KleenePhilippe', None, [{'ORTH': 'Philippe', 'OP': '1'}, + {'ORTH': 'Philippe', 'OP': '+'}]) m = matcher(doc) assert len(m) == 1 - - diff --git a/spacy/tests/test_misc.py b/spacy/tests/test_misc.py index 41c4efb8a..80b859c70 100644 --- a/spacy/tests/test_misc.py +++ b/spacy/tests/test_misc.py @@ -2,12 +2,59 @@ from __future__ import unicode_literals from ..util import ensure_path +from .. import util +from ..displacy import parse_deps, parse_ents +from ..tokens import Span +from .util import get_doc from pathlib import Path import pytest +from thinc.neural import Maxout, Softmax +from thinc.api import chain @pytest.mark.parametrize('text', ['hello/world', 'hello world']) def test_util_ensure_path_succeeds(text): - path = ensure_path(text) + path = util.ensure_path(text) assert isinstance(path, Path) + + +@pytest.mark.parametrize('package', ['numpy']) +def test_util_is_package(package): + """Test that an installed package via pip is recognised by util.is_package.""" + assert util.is_package(package) + + +@pytest.mark.parametrize('package', ['thinc']) +def test_util_get_package_path(package): + """Test that a Path object is returned for a package name.""" + path = util.get_package_path(package) + assert isinstance(path, Path) + + +def test_displacy_parse_ents(en_vocab): + """Test that named entities on a Doc are converted into displaCy's format.""" + doc = get_doc(en_vocab, words=["But", "Google", "is", "starting", "from", "behind"]) + doc.ents = [Span(doc, 1, 2, label=doc.vocab.strings[u'ORG'])] + ents = parse_ents(doc) + assert isinstance(ents, dict) + assert ents['text'] == 'But Google is starting from behind ' + assert ents['ents'] == [{'start': 4, 'end': 10, 'label': 'ORG'}] + + +def test_displacy_parse_deps(en_vocab): + """Test that deps and tags on a Doc are converted into displaCy's format.""" + words = ["This", "is", "a", "sentence"] + heads = [1, 0, 1, -2] + tags = ['DT', 'VBZ', 'DT', 'NN'] + deps = ['nsubj', 'ROOT', 'det', 'attr'] + doc = get_doc(en_vocab, words=words, heads=heads, tags=tags, deps=deps) + deps = parse_deps(doc) + assert isinstance(deps, dict) + assert deps['words'] == [{'text': 'This', 'tag': 'DT'}, + {'text': 'is', 'tag': 'VBZ'}, + {'text': 'a', 'tag': 'DT'}, + {'text': 'sentence', 'tag': 'NN'}] + assert deps['arcs'] == [{'start': 0, 'end': 1, 'label': 'nsubj', 'dir': 'left'}, + {'start': 2, 'end': 3, 'label': 'det', 'dir': 'left'}, + {'start': 1, 'end': 3, 'label': 'attr', 'dir': 'right'}] diff --git a/spacy/tests/tokenizer/test_exceptions.py b/spacy/tests/tokenizer/test_exceptions.py index aab27714e..57281b998 100644 --- a/spacy/tests/tokenizer/test_exceptions.py +++ b/spacy/tests/tokenizer/test_exceptions.py @@ -1,7 +1,4 @@ # coding: utf-8 -"""Test that tokenizer exceptions and emoticons are handled correctly.""" - - from __future__ import unicode_literals import pytest @@ -39,3 +36,10 @@ def test_tokenizer_handles_emoticons(tokenizer): def test_tokenizer_excludes_false_pos_emoticons(tokenizer, text, length): tokens = tokenizer(text) assert len(tokens) == length + + +@pytest.mark.parametrize('text,length', [('can you still dunk?🍕🍔😵LOL', 8), + ('i💙you', 3), ('🤘🤘yay!', 4)]) +def test_tokenizer_handles_emoji(tokenizer, text, length): + tokens = tokenizer(text) + assert len(tokens) == length diff --git a/spacy/tests/tokenizer/test_naughty_strings.py b/spacy/tests/tokenizer/test_naughty_strings.py new file mode 100644 index 000000000..57e39e151 --- /dev/null +++ b/spacy/tests/tokenizer/test_naughty_strings.py @@ -0,0 +1,143 @@ +# coding: utf-8 +from __future__ import unicode_literals + +import pytest + +# Examples taken from the "Big List of Naughty Strings" +# https://github.com/minimaxir/big-list-of-naughty-strings + + +NAUGHTY_STRINGS = [ + # ASCII punctuation + ",./;'[]\-=", + '<>?:"{}|_+', + '!@#$%^&*()`~"', + + # Unicode additional control characters, byte order marks + "­؀؁؂؃؄؅؜۝܏᠎​‌‍‎‏‪", + "￾", + + # Unicode Symbols + "Ω≈ç√∫˜µ≤≥÷", + "åß∂ƒ©˙∆˚¬…æ", + "œ∑´®†¥¨ˆøπ“‘", + "¡™£¢∞§¶•ªº–≠", + "¸˛Ç◊ı˜Â¯˘¿", + "ÅÍÎÏ˝ÓÔÒÚÆ☃", + "Œ„´‰ˇÁ¨ˆØ∏”’", + "`⁄€‹›fifl‡°·‚—±", + "⅛⅜⅝⅞", + "ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя", + "٠١٢٣٤٥٦٧٨٩", + + # Unicode Subscript/Superscript/Accents + "⁰⁴⁵", + "₀₁₂", + "⁰⁴⁵₀₁₂", + "ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็", + + # Two-Byte Characters + "田中さんにあげて下さい", + "パーティーへ行かないか", + "和製漢語", + "部落格", + "사회과학원 어학연구소", + "찦차를 타고 온 펲시맨과 쑛다리 똠방각하", + "社會科學院語學研究所", + "울란바토르", + "𠜎𠜱𠝹𠱓𠱸𠲖𠳏", + + # Japanese Emoticons + "ヽ༼ຈل͜ຈ༽ノ ヽ༼ຈل͜ຈ༽ノ", + "(。◕ ∀ ◕。)", + "`ィ(´∀`∩", + "__ロ(,_,*)", + "・( ̄∀ ̄)・:*:", + "゚・✿ヾ╲(。◕‿◕。)╱✿・゚", + ",。・:*:・゜’( ☻ ω ☻ )。・:*:・゜’", + "(╯°□°)╯︵ ┻━┻)" + "(ノಥ益ಥ)ノ ┻━┻", + "┬─┬ノ( º _ ºノ)", + "( ͡° ͜ʖ ͡°)", + + # Emoji + "😍", + "👩🏽", + "👾 🙇 💁 🙅 🙆 🙋 🙎 🙍", + "🐵 🙈 🙉 🙊", + "❤️ 💔 💌 💕 💞 💓 💗 💖 💘 💝 💟 💜 💛 💚 💙", + "✋🏿 💪🏿 👐🏿 🙌🏿 👏🏿 🙏🏿", + "🚾 🆒 🆓 🆕 🆖 🆗 🆙 🏧", + "0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟", + + # Regional Indicator Symbols + "🇺🇸🇷🇺🇸 🇦🇫🇦🇲🇸", + "🇺🇸🇷🇺🇸🇦🇫🇦🇲", + "🇺🇸🇷🇺🇸🇦", + + # Unicode Numbers + "123", + "١٢٣", + + # Right-To-Left Strings + + "ثم نفس سقطت وبالتحديد،, جزيرتي باستخدام أن دنو. إذ هنا؟ الستار وتنصيب كان. أهّل ايطاليا، بريطانيا-فرنسا قد أخذ. سليمان، إتفاقية بين ما, يذكر الحدود أي بعد, معاملة بولندا، الإطلاق عل إيو.", + "إيو.", + "בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ", + "הָיְתָהtestالصفحات التّحول", + "﷽", + "ﷺ", + "مُنَاقَشَةُ سُبُلِ اِسْتِخْدَامِ اللُّغَةِ فِي النُّظُمِ الْقَائِمَةِ وَفِيم يَخُصَّ التَّطْبِيقَاتُ الْحاسُوبِيَّةُ،", + + # Trick Unicode + "‪‪test‪", + "‫test", + "
test
", + "test⁠test", + "⁦test⁧", + + # Zalgo Text + "Ṱ̺̺̕o͞ ̷i̲̬͇̪͙n̝̗͕v̟̜̘̦͟o̶̙̰̠kè͚̮̺̪̹̱̤ ̖t̝͕̳̣̻̪͞h̼͓̲̦̳̘̲e͇̣̰̦̬͎ ̢̼̻̱̘h͚͎͙̜̣̲ͅi̦̲̣̰̤v̻͍e̺̭̳̪̰-m̢iͅn̖̺̞̲̯̰d̵̼̟͙̩̼̘̳ ̞̥̱̳̭r̛̗̘e͙p͠r̼̞̻̭̗e̺̠̣͟s̘͇̳͍̝͉e͉̥̯̞̲͚̬͜ǹ̬͎͎̟̖͇̤t͍̬̤͓̼̭͘ͅi̪̱n͠g̴͉ ͏͉ͅc̬̟h͡a̫̻̯͘o̫̟̖͍̙̝͉s̗̦̲.̨̹͈̣", + + + "̡͓̞ͅI̗̘̦͝n͇͇͙v̮̫ok̲̫̙͈i̖͙̭̹̠̞n̡̻̮̣̺g̲͈͙̭͙̬͎ ̰t͔̦h̞̲e̢̤ ͍̬̲͖f̴̘͕̣è͖ẹ̥̩l͖͔͚i͓͚̦͠n͖͍̗͓̳̮g͍ ̨o͚̪͡f̘̣̬ ̖̘͖̟͙̮c҉͔̫͖͓͇͖ͅh̵̤̣͚͔á̗̼͕ͅo̼̣̥s̱͈̺̖̦̻͢.̛̖̞̠̫̰", + + + "̗̺͖̹̯͓Ṯ̤͍̥͇͈h̲́e͏͓̼̗̙̼̣͔ ͇̜̱̠͓͍ͅN͕͠e̗̱z̘̝̜̺͙p̤̺̹͍̯͚e̠̻̠͜r̨̤͍̺̖͔̖̖d̠̟̭̬̝͟i̦͖̩͓͔̤a̠̗̬͉̙n͚͜ ̻̞̰͚ͅh̵͉i̳̞v̢͇ḙ͎͟-҉̭̩̼͔m̤̭̫i͕͇̝̦n̗͙ḍ̟ ̯̲͕͞ǫ̟̯̰̲͙̻̝f ̪̰̰̗̖̭̘͘c̦͍̲̞͍̩̙ḥ͚a̮͎̟̙͜ơ̩̹͎s̤.̝̝ ҉Z̡̖̜͖̰̣͉̜a͖̰͙̬͡l̲̫̳͍̩g̡̟̼̱͚̞̬ͅo̗͜.̟", + + + "̦H̬̤̗̤͝e͜ ̜̥̝̻͍̟́w̕h̖̯͓o̝͙̖͎̱̮ ҉̺̙̞̟͈W̷̼̭a̺̪͍į͈͕̭͙̯̜t̶̼̮s̘͙͖̕ ̠̫̠B̻͍͙͉̳ͅe̵h̵̬͇̫͙i̹͓̳̳̮͎̫̕n͟d̴̪̜̖ ̰͉̩͇͙̲͞ͅT͖̼͓̪͢h͏͓̮̻e̬̝̟ͅ ̤̹̝W͙̞̝͔͇͝ͅa͏͓͔̹̼̣l̴͔̰̤̟͔ḽ̫.͕", + + + "Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓G̻O̭̗̮", + + + # Unicode Upsidedown + "˙ɐnbᴉlɐ ɐuƃɐɯ ǝɹolop ʇǝ ǝɹoqɐl ʇn ʇunpᴉpᴉɔuᴉ ɹodɯǝʇ poɯsnᴉǝ op pǝs 'ʇᴉlǝ ƃuᴉɔsᴉdᴉpɐ ɹnʇǝʇɔǝsuoɔ 'ʇǝɯɐ ʇᴉs ɹolop ɯnsdᴉ ɯǝɹo˥", + "00˙Ɩ$-", + + # Unicode font + "The quick brown fox jumps over the lazy dog", + "𝐓𝐡𝐞 𝐪𝐮𝐢𝐜𝐤 𝐛𝐫𝐨𝐰𝐧 𝐟𝐨𝐱 𝐣𝐮𝐦𝐩𝐬 𝐨𝐯𝐞𝐫 𝐭𝐡𝐞 𝐥𝐚𝐳𝐲 𝐝𝐨𝐠", + "𝕿𝖍𝖊 𝖖𝖚𝖎𝖈𝖐 𝖇𝖗𝖔𝖜𝖓 𝖋𝖔𝖝 𝖏𝖚𝖒𝖕𝖘 𝖔𝖛𝖊𝖗 𝖙𝖍𝖊 𝖑𝖆𝖟𝖞 𝖉𝖔𝖌", + "𝑻𝒉𝒆 𝒒𝒖𝒊𝒄𝒌 𝒃𝒓𝒐𝒘𝒏 𝒇𝒐𝒙 𝒋𝒖𝒎𝒑𝒔 𝒐𝒗𝒆𝒓 𝒕𝒉𝒆 𝒍𝒂𝒛𝒚 𝒅𝒐𝒈", + "𝓣𝓱𝓮 𝓺𝓾𝓲𝓬𝓴 𝓫𝓻𝓸𝔀𝓷 𝓯𝓸𝔁 𝓳𝓾𝓶𝓹𝓼 𝓸𝓿𝓮𝓻 𝓽𝓱𝓮 𝓵𝓪𝔃𝔂 𝓭𝓸𝓰", + "𝕋𝕙𝕖 𝕢𝕦𝕚𝕔𝕜 𝕓𝕣𝕠𝕨𝕟 𝕗𝕠𝕩 𝕛𝕦𝕞𝕡𝕤 𝕠𝕧𝕖𝕣 𝕥𝕙𝕖 𝕝𝕒𝕫𝕪 𝕕𝕠𝕘", + "𝚃𝚑𝚎 𝚚𝚞𝚒𝚌𝚔 𝚋𝚛𝚘𝚠𝚗 𝚏𝚘𝚡 𝚓𝚞𝚖𝚙𝚜 𝚘𝚟𝚎𝚛 𝚝𝚑𝚎 𝚕𝚊𝚣𝚢 𝚍𝚘𝚐", + "⒯⒣⒠ ⒬⒰⒤⒞⒦ ⒝⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢", + + # File paths + "../../../../../../../../../../../etc/passwd%00", + "../../../../../../../../../../../etc/hosts", + + # iOS Vulnerabilities + "Powerلُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ冗", + "🏳0🌈️" +] + + +@pytest.mark.slow +@pytest.mark.parametrize('text', NAUGHTY_STRINGS) +def test_tokenizer_naughty_strings(tokenizer, text): + tokens = tokenizer(text) + assert tokens.text_with_ws == text diff --git a/spacy/tests/util.py b/spacy/tests/util.py index 355a4ecae..56aeb5223 100644 --- a/spacy/tests/util.py +++ b/spacy/tests/util.py @@ -3,15 +3,50 @@ from __future__ import unicode_literals from ..tokens import Doc from ..attrs import ORTH, POS, HEAD, DEP +from ..compat import path2str +import pytest import numpy +import tempfile +import shutil +import contextlib +import msgpack +from pathlib import Path + + +MODELS = {} + + +def load_test_model(model): + """Load a model if it's installed as a package, otherwise skip.""" + if model not in MODELS: + module = pytest.importorskip(model) + MODELS[model] = module.load() + return MODELS[model] + + +@contextlib.contextmanager +def make_tempfile(mode='r'): + f = tempfile.TemporaryFile(mode=mode) + yield f + f.close() + + +@contextlib.contextmanager +def make_tempdir(): + d = Path(tempfile.mkdtemp()) + yield d + shutil.rmtree(path2str(d)) def get_doc(vocab, words=[], pos=None, heads=None, deps=None, tags=None, ents=None): """Create Doc object from given vocab, words and annotations.""" pos = pos or [''] * len(words) + tags = tags or [''] * len(words) heads = heads or [0] * len(words) deps = deps or [''] * len(words) + for value in (deps+tags+pos): + vocab.strings.add(value) doc = Doc(vocab, words=words) attrs = doc.to_array([POS, HEAD, DEP]) @@ -71,3 +106,13 @@ def assert_docs_equal(doc1, doc2): assert [ t.ent_type for t in doc1 ] == [ t.ent_type for t in doc2 ] assert [ t.ent_iob for t in doc1 ] == [ t.ent_iob for t in doc2 ] assert [ ent for ent in doc1.ents ] == [ ent for ent in doc2.ents ] + + +def assert_packed_msg_equal(b1, b2): + """Assert that two packed msgpack messages are equal.""" + msg1 = msgpack.loads(b1, encoding='utf8') + msg2 = msgpack.loads(b2, encoding='utf8') + assert sorted(msg1.keys()) == sorted(msg2.keys()) + for (k1, v1), (k2, v2) in zip(sorted(msg1.items()), sorted(msg2.items())): + assert k1 == k2 + assert v1 == v2 diff --git a/spacy/tests/vectors/test_similarity.py b/spacy/tests/vectors/test_similarity.py index 5819ca219..1260728be 100644 --- a/spacy/tests/vectors/test_similarity.py +++ b/spacy/tests/vectors/test_similarity.py @@ -14,9 +14,10 @@ def vectors(): @pytest.fixture() def vocab(en_vocab, vectors): - return add_vecs_to_vocab(en_vocab, vectors) - + #return add_vecs_to_vocab(en_vocab, vectors) + return None +@pytest.mark.xfail def test_vectors_similarity_LL(vocab, vectors): [(word1, vec1), (word2, vec2)] = vectors lex1 = vocab[word1] @@ -30,6 +31,7 @@ def test_vectors_similarity_LL(vocab, vectors): assert numpy.isclose(lex2.similarity(lex2), lex1.similarity(lex1)) +@pytest.mark.xfail def test_vectors_similarity_TT(vocab, vectors): [(word1, vec1), (word2, vec2)] = vectors doc = get_doc(vocab, words=[word1, word2]) @@ -42,18 +44,21 @@ def test_vectors_similarity_TT(vocab, vectors): assert numpy.isclose(doc[1].similarity(doc[0]), doc[0].similarity(doc[1])) +@pytest.mark.xfail def test_vectors_similarity_TD(vocab, vectors): [(word1, vec1), (word2, vec2)] = vectors doc = get_doc(vocab, words=[word1, word2]) assert doc.similarity(doc[0]) == doc[0].similarity(doc) +@pytest.mark.xfail def test_vectors_similarity_DS(vocab, vectors): [(word1, vec1), (word2, vec2)] = vectors doc = get_doc(vocab, words=[word1, word2]) assert doc.similarity(doc[:2]) == doc[:2].similarity(doc) +@pytest.mark.xfail def test_vectors_similarity_TS(vocab, vectors): [(word1, vec1), (word2, vec2)] = vectors doc = get_doc(vocab, words=[word1, word2]) diff --git a/spacy/tests/vectors/test_vectors.py b/spacy/tests/vectors/test_vectors.py index 58a81e2fa..c42c3a4ce 100644 --- a/spacy/tests/vectors/test_vectors.py +++ b/spacy/tests/vectors/test_vectors.py @@ -1,126 +1,166 @@ # coding: utf-8 from __future__ import unicode_literals -from ...tokenizer import Tokenizer -from ..util import get_doc, add_vecs_to_vocab +from ...vectors import Vectors +import numpy import pytest @pytest.fixture -def vectors(): - return [("apple", [0.0, 1.0, 2.0]), ("orange", [3.0, -2.0, 4.0])] +def strings(): + return ["apple", "orange"] + +@pytest.fixture +def data(): + return numpy.asarray([[0.0, 1.0, 2.0], [3.0, -2.0, 4.0]], dtype='f') -@pytest.fixture() -def vocab(en_vocab, vectors): - return add_vecs_to_vocab(en_vocab, vectors) +def test_init_vectors_with_data(strings, data): + v = Vectors(strings, data) + assert v.shape == data.shape + +def test_init_vectors_with_width(strings): + v = Vectors(strings, 3) + assert v.shape == (len(strings), 3) -@pytest.fixture() -def tokenizer_v(vocab): - return Tokenizer(vocab, {}, None, None, None) +def test_get_vector(strings, data): + v = Vectors(strings, data) + assert list(v[strings[0]]) == list(data[0]) + assert list(v[strings[0]]) != list(data[1]) + assert list(v[strings[1]]) != list(data[0]) -@pytest.mark.parametrize('text', ["apple and orange"]) -def test_vectors_token_vector(tokenizer_v, vectors, text): - doc = tokenizer_v(text) - assert vectors[0] == (doc[0].text, list(doc[0].vector)) - assert vectors[1] == (doc[2].text, list(doc[2].vector)) +def test_set_vector(strings, data): + orig = data.copy() + v = Vectors(strings, data) + assert list(v[strings[0]]) == list(orig[0]) + assert list(v[strings[0]]) != list(orig[1]) + v[strings[0]] = data[1] + assert list(v[strings[0]]) == list(orig[1]) + assert list(v[strings[0]]) != list(orig[0]) -@pytest.mark.parametrize('text', ["apple", "orange"]) -def test_vectors_lexeme_vector(vocab, text): - lex = vocab[text] - assert list(lex.vector) - assert lex.vector_norm - - -@pytest.mark.parametrize('text', [["apple", "and", "orange"]]) -def test_vectors_doc_vector(vocab, text): - doc = get_doc(vocab, text) - assert list(doc.vector) - assert doc.vector_norm - - -@pytest.mark.parametrize('text', [["apple", "and", "orange"]]) -def test_vectors_span_vector(vocab, text): - span = get_doc(vocab, text)[0:2] - assert list(span.vector) - assert span.vector_norm - - -@pytest.mark.parametrize('text', ["apple orange"]) -def test_vectors_token_token_similarity(tokenizer_v, text): - doc = tokenizer_v(text) - assert doc[0].similarity(doc[1]) == doc[1].similarity(doc[0]) - assert 0.0 < doc[0].similarity(doc[1]) < 1.0 - - -@pytest.mark.parametrize('text1,text2', [("apple", "orange")]) -def test_vectors_token_lexeme_similarity(tokenizer_v, vocab, text1, text2): - token = tokenizer_v(text1) - lex = vocab[text2] - assert token.similarity(lex) == lex.similarity(token) - assert 0.0 < token.similarity(lex) < 1.0 - - -@pytest.mark.parametrize('text', [["apple", "orange", "juice"]]) -def test_vectors_token_span_similarity(vocab, text): - doc = get_doc(vocab, text) - assert doc[0].similarity(doc[1:3]) == doc[1:3].similarity(doc[0]) - assert 0.0 < doc[0].similarity(doc[1:3]) < 1.0 - - -@pytest.mark.parametrize('text', [["apple", "orange", "juice"]]) -def test_vectors_token_doc_similarity(vocab, text): - doc = get_doc(vocab, text) - assert doc[0].similarity(doc) == doc.similarity(doc[0]) - assert 0.0 < doc[0].similarity(doc) < 1.0 - - -@pytest.mark.parametrize('text', [["apple", "orange", "juice"]]) -def test_vectors_lexeme_span_similarity(vocab, text): - doc = get_doc(vocab, text) - lex = vocab[text[0]] - assert lex.similarity(doc[1:3]) == doc[1:3].similarity(lex) - assert 0.0 < doc.similarity(doc[1:3]) < 1.0 - - -@pytest.mark.parametrize('text1,text2', [("apple", "orange")]) -def test_vectors_lexeme_lexeme_similarity(vocab, text1, text2): - lex1 = vocab[text1] - lex2 = vocab[text2] - assert lex1.similarity(lex2) == lex2.similarity(lex1) - assert 0.0 < lex1.similarity(lex2) < 1.0 - - -@pytest.mark.parametrize('text', [["apple", "orange", "juice"]]) -def test_vectors_lexeme_doc_similarity(vocab, text): - doc = get_doc(vocab, text) - lex = vocab[text[0]] - assert lex.similarity(doc) == doc.similarity(lex) - assert 0.0 < lex.similarity(doc) < 1.0 - - -@pytest.mark.parametrize('text', [["apple", "orange", "juice"]]) -def test_vectors_span_span_similarity(vocab, text): - doc = get_doc(vocab, text) - assert doc[0:2].similarity(doc[1:3]) == doc[1:3].similarity(doc[0:2]) - assert 0.0 < doc[0:2].similarity(doc[1:3]) < 1.0 - - -@pytest.mark.parametrize('text', [["apple", "orange", "juice"]]) -def test_vectors_span_doc_similarity(vocab, text): - doc = get_doc(vocab, text) - assert doc[0:2].similarity(doc) == doc.similarity(doc[0:2]) - assert 0.0 < doc[0:2].similarity(doc) < 1.0 - - -@pytest.mark.parametrize('text1,text2', [ - (["apple", "and", "apple", "pie"], ["orange", "juice"])]) -def test_vectors_doc_doc_similarity(vocab, text1, text2): - doc1 = get_doc(vocab, text1) - doc2 = get_doc(vocab, text2) - assert doc1.similarity(doc2) == doc2.similarity(doc1) - assert 0.0 < doc1.similarity(doc2) < 1.0 +# +#@pytest.fixture() +#def tokenizer_v(vocab): +# return Tokenizer(vocab, {}, None, None, None) +# +# +#@pytest.mark.xfail +#@pytest.mark.parametrize('text', ["apple and orange"]) +#def test_vectors_token_vector(tokenizer_v, vectors, text): +# doc = tokenizer_v(text) +# assert vectors[0] == (doc[0].text, list(doc[0].vector)) +# assert vectors[1] == (doc[2].text, list(doc[2].vector)) +# +# +#@pytest.mark.xfail +#@pytest.mark.parametrize('text', ["apple", "orange"]) +#def test_vectors_lexeme_vector(vocab, text): +# lex = vocab[text] +# assert list(lex.vector) +# assert lex.vector_norm +# +# +#@pytest.mark.xfail +#@pytest.mark.parametrize('text', [["apple", "and", "orange"]]) +#def test_vectors_doc_vector(vocab, text): +# doc = get_doc(vocab, text) +# assert list(doc.vector) +# assert doc.vector_norm +# +# +#@pytest.mark.xfail +#@pytest.mark.parametrize('text', [["apple", "and", "orange"]]) +#def test_vectors_span_vector(vocab, text): +# span = get_doc(vocab, text)[0:2] +# assert list(span.vector) +# assert span.vector_norm +# +# +#@pytest.mark.xfail +#@pytest.mark.parametrize('text', ["apple orange"]) +#def test_vectors_token_token_similarity(tokenizer_v, text): +# doc = tokenizer_v(text) +# assert doc[0].similarity(doc[1]) == doc[1].similarity(doc[0]) +# assert 0.0 < doc[0].similarity(doc[1]) < 1.0 +# +# +#@pytest.mark.xfail +#@pytest.mark.parametrize('text1,text2', [("apple", "orange")]) +#def test_vectors_token_lexeme_similarity(tokenizer_v, vocab, text1, text2): +# token = tokenizer_v(text1) +# lex = vocab[text2] +# assert token.similarity(lex) == lex.similarity(token) +# assert 0.0 < token.similarity(lex) < 1.0 +# +# +#@pytest.mark.xfail +#@pytest.mark.parametrize('text', [["apple", "orange", "juice"]]) +#def test_vectors_token_span_similarity(vocab, text): +# doc = get_doc(vocab, text) +# assert doc[0].similarity(doc[1:3]) == doc[1:3].similarity(doc[0]) +# assert 0.0 < doc[0].similarity(doc[1:3]) < 1.0 +# +# +#@pytest.mark.xfail +#@pytest.mark.parametrize('text', [["apple", "orange", "juice"]]) +#def test_vectors_token_doc_similarity(vocab, text): +# doc = get_doc(vocab, text) +# assert doc[0].similarity(doc) == doc.similarity(doc[0]) +# assert 0.0 < doc[0].similarity(doc) < 1.0 +# +# +#@pytest.mark.xfail +#@pytest.mark.parametrize('text', [["apple", "orange", "juice"]]) +#def test_vectors_lexeme_span_similarity(vocab, text): +# doc = get_doc(vocab, text) +# lex = vocab[text[0]] +# assert lex.similarity(doc[1:3]) == doc[1:3].similarity(lex) +# assert 0.0 < doc.similarity(doc[1:3]) < 1.0 +# +# +#@pytest.mark.xfail +#@pytest.mark.parametrize('text1,text2', [("apple", "orange")]) +#def test_vectors_lexeme_lexeme_similarity(vocab, text1, text2): +# lex1 = vocab[text1] +# lex2 = vocab[text2] +# assert lex1.similarity(lex2) == lex2.similarity(lex1) +# assert 0.0 < lex1.similarity(lex2) < 1.0 +# +# +#@pytest.mark.xfail +#@pytest.mark.parametrize('text', [["apple", "orange", "juice"]]) +#def test_vectors_lexeme_doc_similarity(vocab, text): +# doc = get_doc(vocab, text) +# lex = vocab[text[0]] +# assert lex.similarity(doc) == doc.similarity(lex) +# assert 0.0 < lex.similarity(doc) < 1.0 +# +# +#@pytest.mark.xfail +#@pytest.mark.parametrize('text', [["apple", "orange", "juice"]]) +#def test_vectors_span_span_similarity(vocab, text): +# doc = get_doc(vocab, text) +# assert doc[0:2].similarity(doc[1:3]) == doc[1:3].similarity(doc[0:2]) +# assert 0.0 < doc[0:2].similarity(doc[1:3]) < 1.0 +# +# +#@pytest.mark.xfail +#@pytest.mark.parametrize('text', [["apple", "orange", "juice"]]) +#def test_vectors_span_doc_similarity(vocab, text): +# doc = get_doc(vocab, text) +# assert doc[0:2].similarity(doc) == doc.similarity(doc[0:2]) +# assert 0.0 < doc[0:2].similarity(doc) < 1.0 +# +# +#@pytest.mark.xfail +#@pytest.mark.parametrize('text1,text2', [ +# (["apple", "and", "apple", "pie"], ["orange", "juice"])]) +#def test_vectors_doc_doc_similarity(vocab, text1, text2): +# doc1 = get_doc(vocab, text1) +# doc2 = get_doc(vocab, text2) +# assert doc1.similarity(doc2) == doc2.similarity(doc1) +# assert 0.0 < doc1.similarity(doc2) < 1.0 diff --git a/spacy/tests/vocab/test_add_vectors.py b/spacy/tests/vocab/test_add_vectors.py index 38f2f85e8..10477cdf1 100644 --- a/spacy/tests/vocab/test_add_vectors.py +++ b/spacy/tests/vocab/test_add_vectors.py @@ -5,6 +5,7 @@ import numpy import pytest +@pytest.mark.xfail @pytest.mark.parametrize('text', ["Hello"]) def test_vocab_add_vector(en_vocab, text): en_vocab.resize_vectors(10) diff --git a/spacy/tests/vocab/test_lexeme.py b/spacy/tests/vocab/test_lexeme.py index 163df8591..0140b256a 100644 --- a/spacy/tests/vocab/test_lexeme.py +++ b/spacy/tests/vocab/test_lexeme.py @@ -63,7 +63,6 @@ def test_lexeme_bytes_roundtrip(en_vocab): alpha = en_vocab['alpha'] assert one.orth != alpha.orth assert one.lower != alpha.lower - print(one.orth, alpha.orth) alpha.from_bytes(one.to_bytes()) assert one.orth_ == alpha.orth_ diff --git a/spacy/tokenizer.pyx b/spacy/tokenizer.pyx index 05a73ea34..de184baba 100644 --- a/spacy/tokenizer.pyx +++ b/spacy/tokenizer.pyx @@ -2,85 +2,42 @@ # coding: utf8 from __future__ import unicode_literals -import ujson - +from collections import OrderedDict from cython.operator cimport dereference as deref from cython.operator cimport preincrement as preinc from cymem.cymem cimport Pool from preshed.maps cimport PreshMap +import regex as re from .strings cimport hash_string +from . import util cimport cython -from . import util from .tokens.doc cimport Doc cdef class Tokenizer: + """Segment text, and create Doc objects with the discovered segment + boundaries. """ - Segment text, and create Doc objects with the discovered segment boundaries. - """ - @classmethod - def load(cls, path, Vocab vocab, rules=None, prefix_search=None, suffix_search=None, - infix_finditer=None, token_match=None): - """ - Load a Tokenizer, reading unsupplied components from the path. - - Arguments: - path (Path): - The path to load from. - vocab (Vocab): - A storage container for lexical types. - rules (dict): - Exceptions and special-cases for the tokenizer. - token_match: - A boolean function matching strings that becomes tokens. - prefix_search: - Signature of re.compile(string).search - suffix_search: - Signature of re.compile(string).search - infix_finditer: - Signature of re.compile(string).finditer - Returns Tokenizer - """ - path = util.ensure_path(path) - if rules is None: - with (path / 'tokenizer' / 'specials.json').open('r', encoding='utf8') as file_: - rules = ujson.load(file_) - if prefix_search in (None, True): - with (path / 'tokenizer' / 'prefix.txt').open() as file_: - entries = file_.read().split('\n') - prefix_search = util.compile_prefix_regex(entries).search - if suffix_search in (None, True): - with (path / 'tokenizer' / 'suffix.txt').open() as file_: - entries = file_.read().split('\n') - suffix_search = util.compile_suffix_regex(entries).search - if infix_finditer in (None, True): - with (path / 'tokenizer' / 'infix.txt').open() as file_: - entries = file_.read().split('\n') - infix_finditer = util.compile_infix_regex(entries).finditer - return cls(vocab, rules, prefix_search, suffix_search, infix_finditer, token_match) - def __init__(self, Vocab vocab, rules, prefix_search, suffix_search, infix_finditer, token_match=None): - """ - Create a Tokenizer, to create Doc objects given unicode text. + """Create a `Tokenizer`, to create `Doc` objects given unicode text. - Arguments: - vocab (Vocab): - A storage container for lexical types. - rules (dict): - Exceptions and special-cases for the tokenizer. - prefix_search: - A function matching the signature of re.compile(string).search - to match prefixes. - suffix_search: - A function matching the signature of re.compile(string).search - to match suffixes. - infix_finditer: - A function matching the signature of re.compile(string).finditer - to find infixes. - token_match: - A boolean function matching strings that becomes tokens. + vocab (Vocab): A storage container for lexical types. + rules (dict): Exceptions and special-cases for the tokenizer. + prefix_search (callable): A function matching the signature of + `re.compile(string).search` to match prefixes. + suffix_search (callable): A function matching the signature of + `re.compile(string).search` to match suffixes. + `infix_finditer` (callable): A function matching the signature of + `re.compile(string).finditer` to find infixes. + token_match (callable): A boolean function matching strings to be + recognised as tokens. + RETURNS (Tokenizer): The newly constructed object. + + EXAMPLE: + >>> tokenizer = Tokenizer(nlp.vocab) + >>> tokenizer = English().Defaults.create_tokenizer(nlp) """ self.mem = Pool() self._cache = PreshMap() @@ -112,13 +69,10 @@ cdef class Tokenizer: @cython.boundscheck(False) def __call__(self, unicode string): - """ - Tokenize a string. + """Tokenize a string. - Arguments: - string (unicode): The string to tokenize. - Returns: - Doc A container for linguistic annotations. + string (unicode): The string to tokenize. + RETURNS (Doc): A container for linguistic annotations. """ if len(string) >= (2 ** 30): raise ValueError( @@ -166,18 +120,13 @@ cdef class Tokenizer: return tokens def pipe(self, texts, batch_size=1000, n_threads=2): - """ - Tokenize a stream of texts. + """Tokenize a stream of texts. - Arguments: - texts: A sequence of unicode texts. - batch_size (int): - The number of texts to accumulate in an internal buffer. - n_threads (int): - The number of threads to use, if the implementation supports - multi-threading. The default tokenizer is single-threaded. - Yields: - Doc A sequence of Doc objects, in order. + texts: A sequence of unicode texts. + batch_size (int): The number of texts to accumulate in an internal buffer. + n_threads (int): The number of threads to use, if the implementation + supports multi-threading. The default tokenizer is single-threaded. + YIELDS (Doc): A sequence of Doc objects, in order. """ for text in texts: yield self(text) @@ -321,27 +270,23 @@ cdef class Tokenizer: self._cache.set(key, cached) def find_infix(self, unicode string): - """ - Find internal split points of the string, such as hyphens. + """Find internal split points of the string, such as hyphens. string (unicode): The string to segment. - - Returns List[re.MatchObject] - A list of objects that have .start() and .end() methods, denoting the - placement of internal segment separators, e.g. hyphens. + RETURNS (list): A list of `re.MatchObject` objects that have `.start()` + and `.end()` methods, denoting the placement of internal segment + separators, e.g. hyphens. """ if self.infix_finditer is None: return 0 return list(self.infix_finditer(string)) def find_prefix(self, unicode string): - """ - Find the length of a prefix that should be segmented from the string, + """Find the length of a prefix that should be segmented from the string, or None if no prefix rules match. - Arguments: - string (unicode): The string to segment. - Returns (int or None): The length of the prefix if present, otherwise None. + string (unicode): The string to segment. + RETURNS (int): The length of the prefix if present, otherwise `None`. """ if self.prefix_search is None: return 0 @@ -349,13 +294,11 @@ cdef class Tokenizer: return (match.end() - match.start()) if match is not None else 0 def find_suffix(self, unicode string): - """ - Find the length of a suffix that should be segmented from the string, + """Find the length of a suffix that should be segmented from the string, or None if no suffix rules match. - Arguments: - string (unicode): The string to segment. - Returns (int or None): The length of the suffix if present, otherwise None. + string (unicode): The string to segment. + Returns (int): The length of the suffix if present, otherwise `None`. """ if self.suffix_search is None: return 0 @@ -363,23 +306,17 @@ cdef class Tokenizer: return (match.end() - match.start()) if match is not None else 0 def _load_special_tokenization(self, special_cases): - """ - Add special-case tokenization rules. - """ + """Add special-case tokenization rules.""" for chunk, substrings in sorted(special_cases.items()): self.add_special_case(chunk, substrings) def add_special_case(self, unicode string, substrings): - """ - Add a special-case tokenization rule. + """Add a special-case tokenization rule. - Arguments: - string (unicode): The string to specially tokenize. - token_attrs: - A sequence of dicts, where each dict describes a token and its - attributes. The ORTH fields of the attributes must exactly match - the string when they are concatenated. - Returns None + string (unicode): The string to specially tokenize. + token_attrs (iterable): A sequence of dicts, where each dict describes + a token and its attributes. The `ORTH` fields of the attributes must + exactly match the string when they are concatenated. """ substrings = list(substrings) cached = <_Cached*>self.mem.alloc(1, sizeof(_Cached)) @@ -390,3 +327,70 @@ cdef class Tokenizer: self._specials.set(key, cached) self._cache.set(key, cached) self._rules[string] = substrings + + def to_disk(self, path, **exclude): + """Save the current state to a directory. + + path (unicode or Path): A path to a directory, which will be created if + it doesn't exist. Paths may be either strings or `Path`-like objects. + """ + with path.open('wb') as file_: + file_.write(self.to_bytes(**exclude)) + + def from_disk(self, path, **exclude): + """Loads state from a directory. Modifies the object in place and + returns it. + + path (unicode or Path): A path to a directory. Paths may be either + strings or `Path`-like objects. + RETURNS (Tokenizer): The modified `Tokenizer` object. + """ + with path.open('rb') as file_: + bytes_data = file_.read() + self.from_bytes(bytes_data, **exclude) + return self + + def to_bytes(self, **exclude): + """Serialize the current state to a binary string. + + **exclude: Named attributes to prevent from being serialized. + RETURNS (bytes): The serialized form of the `Tokenizer` object. + """ + serializers = OrderedDict(( + ('vocab', lambda: self.vocab.to_bytes()), + ('prefix_search', lambda: self.prefix_search.__self__.pattern), + ('suffix_search', lambda: self.suffix_search.__self__.pattern), + ('infix_finditer', lambda: self.infix_finditer.__self__.pattern), + ('token_match', lambda: self.token_match.__self__.pattern), + ('exceptions', lambda: OrderedDict(sorted(self._rules.items()))) + )) + return util.to_bytes(serializers, exclude) + + def from_bytes(self, bytes_data, **exclude): + """Load state from a binary string. + + bytes_data (bytes): The data to load from. + **exclude: Named attributes to prevent from being loaded. + RETURNS (Tokenizer): The `Tokenizer` object. + """ + data = OrderedDict() + deserializers = OrderedDict(( + ('vocab', lambda b: self.vocab.from_bytes(b)), + ('prefix_search', lambda b: data.setdefault('prefix', b)), + ('suffix_search', lambda b: data.setdefault('suffix_search', b)), + ('infix_finditer', lambda b: data.setdefault('infix_finditer', b)), + ('token_match', lambda b: data.setdefault('token_match', b)), + ('exceptions', lambda b: data.setdefault('rules', b)) + )) + msg = util.from_bytes(bytes_data, deserializers, exclude) + if 'prefix_search' in data: + self.prefix_search = re.compile(data['prefix_search']).search + if 'suffix_search' in data: + self.suffix_search = re.compile(data['suffix_search']).search + if 'infix_finditer' in data: + self.infix_finditer = re.compile(data['infix_finditer']).finditer + if 'token_match' in data: + self.token_match = re.compile(data['token_match']).search + for string, substrings in data.get('rules', {}).items(): + self.add_special_case(string, substrings) + return self diff --git a/spacy/tokens/doc.pxd b/spacy/tokens/doc.pxd index ef1f85ea7..d0c83e0f8 100644 --- a/spacy/tokens/doc.pxd +++ b/spacy/tokens/doc.pxd @@ -33,6 +33,7 @@ cdef class Doc: cdef public object _vector_norm cdef public object tensor + cdef public object cats cdef public object user_data cdef TokenC* c diff --git a/spacy/tokens/doc.pyx b/spacy/tokens/doc.pyx index 949fdea29..822a0152d 100644 --- a/spacy/tokens/doc.pyx +++ b/spacy/tokens/doc.pyx @@ -11,7 +11,6 @@ import struct import dill from libc.string cimport memcpy, memset -from libc.stdint cimport uint32_t from libc.math cimport sqrt from .span cimport Span @@ -21,14 +20,16 @@ from .token cimport Token from .printers import parse_tree from ..lexeme cimport Lexeme, EMPTY_LEXEME from ..typedefs cimport attr_t, flags_t +from ..attrs import intify_attrs from ..attrs cimport attr_id_t from ..attrs cimport ID, ORTH, NORM, LOWER, SHAPE, PREFIX, SUFFIX, LENGTH, CLUSTER from ..attrs cimport LENGTH, POS, LEMMA, TAG, DEP, HEAD, SPACY, ENT_IOB, ENT_TYPE +from ..attrs cimport SENT_START from ..parts_of_speech cimport CCONJ, PUNCT, NOUN, univ_pos_t -from ..syntax.iterators import CHUNKERS from ..util import normalize_slice from ..compat import is_config from .. import about +from .. import util DEF PADDING = 5 @@ -52,6 +53,8 @@ cdef attr_t get_token_attr(const TokenC* token, attr_id_t feat_name) nogil: return token.dep elif feat_name == HEAD: return token.head + elif feat_name == SENT_START: + return token.sent_start elif feat_name == SPACY: return token.spacy elif feat_name == ENT_IOB: @@ -61,6 +64,14 @@ cdef attr_t get_token_attr(const TokenC* token, attr_id_t feat_name) nogil: else: return Lexeme.get_struct_attr(token.lex, feat_name) +def _get_chunker(lang): + try: + cls = util.get_lang_class(lang) + except ImportError: + return None + except KeyError: + return None + return cls.Defaults.syntax_iterators.get(u'noun_chunks') cdef class Doc: """A sequence of Token objects. Access sentences and named entities, export @@ -106,6 +117,7 @@ cdef class Doc: self.is_tagged = False self.is_parsed = False self.sentiment = 0.0 + self.cats = {} self.user_hooks = {} self.user_token_hooks = {} self.user_span_hooks = {} @@ -113,7 +125,7 @@ cdef class Doc: self.user_data = {} self._py_tokens = [] self._vector = None - self.noun_chunks_iterator = CHUNKERS.get(self.vocab.lang) + self.noun_chunks_iterator = _get_chunker(self.vocab.lang) cdef unicode orth cdef bint has_space if orths_and_spaces is None and words is not None: @@ -150,6 +162,10 @@ cdef class Doc: def __getitem__(self, object i): """Get a `Token` or `Span` object. + i (int or tuple) The index of the token, or the slice of the document to get. + RETURNS (Token or Span): The token at `doc[i]]`, or the span at + `doc[start : end]`. + EXAMPLE: >>> doc[i] Get the `Token` object at position `i`, where `i` is an integer. @@ -197,6 +213,8 @@ cdef class Doc: def __len__(self): """The number of tokens in the document. + RETURNS (int): The number of tokens in the document. + EXAMPLE: >>> len(doc) """ @@ -243,8 +261,12 @@ cdef class Doc: def __get__(self): if 'has_vector' in self.user_hooks: return self.user_hooks['has_vector'](self) - - return any(token.has_vector for token in self) + elif any(token.has_vector for token in self): + return True + elif self.tensor is not None: + return True + else: + return False property vector: """A real-valued meaning representation. Defaults to an average of the @@ -256,18 +278,25 @@ cdef class Doc: def __get__(self): if 'vector' in self.user_hooks: return self.user_hooks['vector'](self) - if self._vector is None: - if len(self): - self._vector = sum(t.vector for t in self) / len(self) - else: - return numpy.zeros((self.vocab.vectors_length,), dtype='float32') - return self._vector + if self._vector is not None: + return self._vector + elif self.has_vector and len(self): + self._vector = sum(t.vector for t in self) / len(self) + return self._vector + elif self.tensor is not None: + self._vector = self.tensor.mean(axis=0) + return self._vector + else: + return numpy.zeros((self.vocab.vectors_length,), dtype='float32') def __set__(self, value): self._vector = value property vector_norm: - # TODO: docstrings / docs + """The L2 norm of the document's vector representation. + + RETURNS (float): The L2 norm of the vector representation. + """ def __get__(self): if 'vector_norm' in self.user_hooks: return self.user_hooks['vector_norm'](self) @@ -283,10 +312,6 @@ cdef class Doc: def __set__(self, value): self._vector_norm = value - @property - def string(self): - return self.text - property text: """A unicode representation of the document text. @@ -324,7 +349,7 @@ cdef class Doc: cdef int i cdef const TokenC* token cdef int start = -1 - cdef int label = 0 + cdef attr_t label = 0 output = [] for i in range(self.length): token = &self.c[i] @@ -420,7 +445,8 @@ cdef class Doc: """ def __get__(self): if 'sents' in self.user_hooks: - return self.user_hooks['sents'](self) + yield from self.user_hooks['sents'](self) + return if not self.is_parsed: raise ValueError( @@ -482,8 +508,8 @@ cdef class Doc: cdef np.ndarray[attr_t, ndim=2] output # Make an array from the attributes --- otherwise our inner loop is Python # dict iteration. - cdef np.ndarray[attr_t, ndim=1] attr_ids = numpy.asarray(py_attr_ids, dtype=numpy.int32) - output = numpy.ndarray(shape=(self.length, len(attr_ids)), dtype=numpy.int32) + cdef np.ndarray[attr_t, ndim=1] attr_ids = numpy.asarray(py_attr_ids, dtype=numpy.uint64) + output = numpy.ndarray(shape=(self.length, len(attr_ids)), dtype=numpy.uint64) for i in range(self.length): for j, feature in enumerate(attr_ids): output[i, j] = get_token_attr(&self.c[i], feature) @@ -550,14 +576,16 @@ cdef class Doc: for i in range(self.length): self.c[i] = parsed[i] - def from_array(self, attrs, int[:, :] array): - """Load attributes from a numpy array. Write to a `Doc` object, from an - `(M, N)` array of attributes. - - attrs (ints): A list of attribute ID ints. - array (numpy.ndarray[ndim=2, dtype='int32']) The attribute values to load. - RETURNS (Doc): Itself. - """ + def from_array(self, attrs, array): + if SENT_START in attrs and HEAD in attrs: + raise ValueError( + "Conflicting attributes specified in doc.from_array():\n" + "(HEAD, SENT_START)\n" + "The HEAD attribute currently sets sentence boundaries implicitly,\n" + "based on the tree structure. This means the HEAD attribute would " + "potentially override the sentence boundaries set by SENT_START.\n" + "See https://github.com/spacy-io/spaCy/issues/235 for details and " + "workarounds, and to propose solutions.") cdef int i, col cdef attr_id_t attr_id cdef TokenC* tokens = self.c @@ -584,23 +612,45 @@ cdef class Doc: self.is_tagged = bool(TAG in attrs or POS in attrs) return self - def to_bytes(self): + def to_disk(self, path, **exclude): + """Save the current state to a directory. + + path (unicode or Path): A path to a directory, which will be created if + it doesn't exist. Paths may be either strings or `Path`-like objects. + """ + with path.open('wb') as file_: + file_.write(self.to_bytes(**exclude)) + + def from_disk(self, path, **exclude): + """Loads state from a directory. Modifies the object in place and + returns it. + + path (unicode or Path): A path to a directory. Paths may be either + strings or `Path`-like objects. + RETURNS (Doc): The modified `Doc` object. + """ + with path.open('rb') as file_: + bytes_data = file_.read() + self.from_bytes(bytes_data, **exclude) + + def to_bytes(self, **exclude): """Serialize, i.e. export the document contents to a binary string. RETURNS (bytes): A losslessly serialized copy of the `Doc`, including all annotations. """ - return dill.dumps( - (self.text, - self.to_array([LENGTH,SPACY,TAG,LEMMA,HEAD,DEP,ENT_IOB,ENT_TYPE]), - self.sentiment, - self.tensor, - self.noun_chunks_iterator, - self.user_data, - (self.user_hooks, self.user_token_hooks, self.user_span_hooks)), - protocol=-1) + array_head = [LENGTH,SPACY,TAG,LEMMA,HEAD,DEP,ENT_IOB,ENT_TYPE] + serializers = { + 'text': lambda: self.text, + 'array_head': lambda: array_head, + 'array_body': lambda: self.to_array(array_head), + 'sentiment': lambda: self.sentiment, + 'tensor': lambda: self.tensor, + 'user_data': lambda: self.user_data + } + return util.to_bytes(serializers, exclude) - def from_bytes(self, data): + def from_bytes(self, bytes_data, **exclude): """Deserialize, i.e. import the document contents from a binary string. data (bytes): The string to load from. @@ -608,27 +658,36 @@ cdef class Doc: """ if self.length != 0: raise ValueError("Cannot load into non-empty Doc") - cdef int[:, :] attrs + deserializers = { + 'text': lambda b: None, + 'array_head': lambda b: None, + 'array_body': lambda b: None, + 'sentiment': lambda b: None, + 'tensor': lambda b: None, + 'user_data': lambda user_data: self.user_data.update(user_data) + } + + msg = util.from_bytes(bytes_data, deserializers, exclude) + + cdef attr_t[:, :] attrs cdef int i, start, end, has_space - fields = dill.loads(data) - text, attrs = fields[:2] - self.sentiment, self.tensor = fields[2:4] - self.noun_chunks_iterator, self.user_data = fields[4:6] - self.user_hooks, self.user_token_hooks, self.user_span_hooks = fields[6] + self.sentiment = msg['sentiment'] + self.tensor = msg['tensor'] start = 0 cdef const LexemeC* lex cdef unicode orth_ + text = msg['text'] + attrs = msg['array_body'] for i in range(attrs.shape[0]): end = start + attrs[i, 0] has_space = attrs[i, 1] orth_ = text[start:end] lex = self.vocab.get(self.mem, orth_) self.push_back(lex, has_space) - start = end + has_space - self.from_array([TAG,LEMMA,HEAD,DEP,ENT_IOB,ENT_TYPE], - attrs[:, 2:]) + self.from_array(msg['array_head'][2:], + attrs[:, 2:]) return self def merge(self, int start_idx, int end_idx, *args, **attributes): @@ -647,14 +706,12 @@ cdef class Doc: if len(args) == 3: # TODO: Warn deprecation tag, lemma, ent_type = args - attributes[TAG] = self.vocab.strings[tag] - attributes[LEMMA] = self.vocab.strings[lemma] - attributes[ENT_TYPE] = self.vocab.strings[ent_type] + attributes[TAG] = tag + attributes[LEMMA] = lemma + attributes[ENT_TYPE] = ent_type elif not args: - # TODO: This code makes little sense overall. We're still - # ignoring most of the attributes? if "label" in attributes and 'ent_type' not in attributes: - if type(attributes["label"]) == int: + if isinstance(attributes["label"], int): attributes[ENT_TYPE] = attributes["label"] else: attributes[ENT_TYPE] = self.vocab.strings[attributes["label"]] @@ -667,6 +724,12 @@ cdef class Doc: "Arguments supplied:\n%s\n" "Keyword arguments:%s\n" % (len(args), repr(args), repr(attributes))) + # More deprecated attribute handling =/ + if 'label' in attributes: + attributes['ent_type'] = attributes.pop('label') + + attributes = intify_attrs(attributes, strings_map=self.vocab.strings) + cdef int start = token_by_start(self.c, self.length, start_idx) if start == -1: return None @@ -676,13 +739,6 @@ cdef class Doc: # Currently we have the token index, we want the range-end index end += 1 cdef Span span = self[start:end] - tag = self.vocab.strings[attributes.get(TAG, span.root.tag)] - lemma = self.vocab.strings[attributes.get(LEMMA, span.root.lemma)] - ent_type = self.vocab.strings[attributes.get(ENT_TYPE, span.root.ent_type)] - ent_id = attributes.get('ent_id', span.root.ent_id) - if isinstance(ent_id, basestring): - ent_id = self.vocab.strings[ent_id] - # Get LexemeC for newly merged token new_orth = ''.join([t.text_with_ws for t in span]) if span[-1].whitespace_: @@ -691,18 +747,11 @@ cdef class Doc: # House the new merged token where it starts cdef TokenC* token = &self.c[start] token.spacy = self.c[end-1].spacy - if tag in self.vocab.morphology.tag_map: - self.vocab.morphology.assign_tag(token, tag) - else: - token.tag = self.vocab.strings[tag] - token.lemma = self.vocab.strings[lemma] - if ent_type == 'O': - token.ent_iob = 2 - token.ent_type = 0 - else: - token.ent_iob = 3 - token.ent_type = self.vocab.strings[ent_type] - token.ent_id = ent_id + for attr_name, attr_value in attributes.items(): + if attr_name == TAG: + self.vocab.morphology.assign_tag(token, attr_value) + else: + Token.set_struct_attr(token, attr_name, attr_value) # Begin by setting all the head indices to absolute token positions # This is easier to work with for now than the offsets # Before thinking of something simpler, beware the case where a dependency diff --git a/spacy/tokens/span.pxd b/spacy/tokens/span.pxd index 303933d42..8d675c04f 100644 --- a/spacy/tokens/span.pxd +++ b/spacy/tokens/span.pxd @@ -1,6 +1,7 @@ cimport numpy as np from .doc cimport Doc +from ..typedefs cimport attr_t cdef class Span: @@ -9,7 +10,7 @@ cdef class Span: cdef readonly int end cdef readonly int start_char cdef readonly int end_char - cdef readonly int label + cdef readonly attr_t label cdef public _vector cdef public _vector_norm diff --git a/spacy/tokens/span.pyx b/spacy/tokens/span.pyx index 55330af78..9f2115fe1 100644 --- a/spacy/tokens/span.pyx +++ b/spacy/tokens/span.pyx @@ -21,14 +21,14 @@ from .. import about cdef class Span: """A slice from a Doc object.""" - def __cinit__(self, Doc doc, int start, int end, int label=0, vector=None, + def __cinit__(self, Doc doc, int start, int end, attr_t label=0, vector=None, vector_norm=None): """Create a `Span` object from the slice `doc[start : end]`. doc (Doc): The parent document. start (int): The index of the first token of the span. end (int): The index of the first token after the span. - label (int): A label to attach to the Span, e.g. for named entities. + label (uint64): A label to attach to the Span, e.g. for named entities. vector (ndarray[ndim=1, dtype='float32']): A meaning representation of the span. RETURNS (Span): The newly constructed object. """ @@ -43,6 +43,7 @@ cdef class Span: self.end_char = self.doc[end - 1].idx + len(self.doc[end - 1]) else: self.end_char = 0 + assert label in doc.vocab.strings, label self.label = label self._vector = vector self._vector_norm = vector_norm @@ -66,6 +67,10 @@ cdef class Span: return hash((self.doc, self.label, self.start_char, self.end_char)) def __len__(self): + """Get the number of tokens in the span. + + RETURNS (int): The number of tokens in the span. + """ self._recalculate_indices() if self.end < self.start: return 0 @@ -77,6 +82,16 @@ cdef class Span: return self.text.encode('utf-8') def __getitem__(self, object i): + """Get a `Token` or a `Span` object + + i (int or tuple): The index of the token within the span, or slice of + the span to get. + RETURNS (Token or Span): The token at `span[i]`. + + EXAMPLE: + >>> span[0] + >>> span[1:3] + """ self._recalculate_indices() if isinstance(i, slice): start, end = normalize_slice(len(self), i.start, i.stop, i.step) @@ -88,12 +103,17 @@ cdef class Span: return self.doc[self.start + i] def __iter__(self): + """Iterate over `Token` objects. + + YIELDS (Token): A `Token` object. + """ self._recalculate_indices() for i in range(self.start, self.end): yield self.doc[i] def merge(self, *args, **attributes): - """Retokenize the document, such that the span is merged into a single token. + """Retokenize the document, such that the span is merged into a single + token. **attributes: Attributes to assign to the merged token. By default, attributes are inherited from the syntactic root token of the span. @@ -102,7 +122,7 @@ cdef class Span: return self.doc.merge(self.start_char, self.end_char, *args, **attributes) def similarity(self, other): - """ Make a semantic similarity estimate. The default estimate is cosine + """Make a semantic similarity estimate. The default estimate is cosine similarity using an average of word vectors. other (object): The object to compare with. By default, accepts `Doc`, @@ -149,14 +169,23 @@ cdef class Span: return self.doc[root.l_edge : root.r_edge + 1] property has_vector: - # TODO: docstring + """A boolean value indicating whether a word vector is associated with + the object. + + RETURNS (bool): Whether a word vector is associated with the object. + """ def __get__(self): if 'has_vector' in self.doc.user_span_hooks: return self.doc.user_span_hooks['has_vector'](self) return any(token.has_vector for token in self) property vector: - # TODO: docstring + """A real-valued meaning representation. Defaults to an average of the + token vectors. + + RETURNS (numpy.ndarray[ndim=1, dtype='float32']): A 1D numpy array + representing the span's semantics. + """ def __get__(self): if 'vector' in self.doc.user_span_hooks: return self.doc.user_span_hooks['vector'](self) @@ -165,7 +194,10 @@ cdef class Span: return self._vector property vector_norm: - # TODO: docstring + """The L2 norm of the document's vector representation. + + RETURNS (float): The L2 norm of the vector representation. + """ def __get__(self): if 'vector_norm' in self.doc.user_span_hooks: return self.doc.user_span_hooks['vector'](self) @@ -187,7 +219,10 @@ cdef class Span: return sum([token.sentiment for token in self]) / len(self) property text: - # TODO: docstring + """A unicode representation of the span text. + + RETURNS (unicode): The original verbatim text of the span. + """ def __get__(self): text = self.text_with_ws if self[-1].whitespace_: @@ -195,7 +230,11 @@ cdef class Span: return text property text_with_ws: - # TODO: docstring + """The text content of the span with a trailing whitespace character if + the last token has one. + + RETURNS (unicode): The text content of the span (with trailing whitespace). + """ def __get__(self): return u''.join([t.text_with_ws for t in self]) @@ -218,6 +257,7 @@ cdef class Span: # The tricky thing here is that Span accepts its tokenisation changing, # so it's okay once we have the Span objects. See Issue #375 spans = [] + cdef attr_t label for start, end, label in self.doc.noun_chunks_iterator(self): spans.append(Span(self, start, end, label=label)) for span in spans: @@ -241,15 +281,15 @@ cdef class Span: The head of 'new' is 'York', and the head of "York" is "like" - >>> toks[new].head.orth_ + >>> toks[new].head.text 'York' - >>> toks[york].head.orth_ + >>> toks[york].head.text 'like' Create a span for "New York". Its root is "York". >>> new_york = toks[new:york+1] - >>> new_york.root.orth_ + >>> new_york.root.text 'York' Here's a more complicated case, raised by issue #214: @@ -339,7 +379,7 @@ cdef class Span: property ent_id: """An (integer) entity ID. Usually assigned by patterns in the `Matcher`. - RETURNS (int): The entity ID. + RETURNS (uint64): The entity ID. """ def __get__(self): return self.root.ent_id @@ -370,7 +410,10 @@ cdef class Span: return ''.join([t.string for t in self]).strip() property lemma_: - # TODO: docstring + """The span's lemma. + + RETURNS (unicode): The span's lemma. + """ def __get__(self): return ' '.join([t.lemma_ for t in self]).strip() @@ -390,7 +433,10 @@ cdef class Span: return ''.join([t.string for t in self]) property label_: - # TODO: docstring + """The span's label. + + RETURNS (unicode): The span's label. + """ def __get__(self): return self.doc.vocab.strings[self.label] diff --git a/spacy/tokens/token.pyx b/spacy/tokens/token.pyx index 6430c9f29..5b8c276d8 100644 --- a/spacy/tokens/token.pyx +++ b/spacy/tokens/token.pyx @@ -23,10 +23,14 @@ from .. import about cdef class Token: - """ - An individual token --- i.e. a word, punctuation symbol, whitespace, etc. - """ + """An individual token – i.e. a word, punctuation symbol, whitespace, etc.""" def __cinit__(self, Vocab vocab, Doc doc, int offset): + """Construct a `Token` object. + + vocab (Vocab): A storage container for lexical types. + doc (Doc): The parent document. + offset (int): The index of the token within the document. + """ self.vocab = vocab self.doc = doc self.c = &self.doc.c[offset] @@ -36,8 +40,9 @@ cdef class Token: return hash((self.doc, self.i)) def __len__(self): - """ - Number of unicode characters in token.text. + """The number of unicode characters in the token, i.e. `token.text`. + + RETURNS (int): The number of unicode characters in the token. """ return self.c.lex.length @@ -75,49 +80,51 @@ cdef class Token: raise ValueError(op) cpdef bint check_flag(self, attr_id_t flag_id) except -1: - """ - Check the value of a boolean flag. + """Check the value of a boolean flag. - Arguments: - flag_id (int): The ID of the flag attribute. - Returns: - is_set (bool): Whether the flag is set. + flag_id (int): The ID of the flag attribute. + RETURNS (bool): Whether the flag is set. + + EXAMPLE: + >>> from spacy.attrs import IS_TITLE + >>> doc = nlp(u'Give it back! He pleaded.') + >>> token = doc[0] + >>> token.check_flag(IS_TITLE) + True """ return Lexeme.c_check_flag(self.c.lex, flag_id) def nbor(self, int i=1): - """ - Get a neighboring token. + """Get a neighboring token. - Arguments: - i (int): The relative position of the token to get. Defaults to 1. - Returns: - neighbor (Token): The token at position self.doc[self.i+i] + i (int): The relative position of the token to get. Defaults to 1. + RETURNS (Token): The token at position `self.doc[self.i+i]`. """ return self.doc[self.i+i] def similarity(self, other): - """ - Compute a semantic similarity estimate. Defaults to cosine over vectors. + """Make a semantic similarity estimate. The default estimate is cosine + similarity using an average of word vectors. - Arguments: - other: - The object to compare with. By default, accepts Doc, Span, - Token and Lexeme objects. - Returns: - score (float): A scalar similarity score. Higher is more similar. + other (object): The object to compare with. By default, accepts `Doc`, + `Span`, `Token` and `Lexeme` objects. + RETURNS (float): A scalar similarity score. Higher is more similar. """ if 'similarity' in self.doc.user_token_hooks: - return self.doc.user_token_hooks['similarity'](self) + return self.doc.user_token_hooks['similarity'](self) if self.vector_norm == 0 or other.vector_norm == 0: return 0.0 return numpy.dot(self.vector, other.vector) / (self.vector_norm * other.vector_norm) property lex_id: + """ID of the token's lexical type. + + RETURNS (int): ID of the token's lexical type.""" def __get__(self): return self.c.lex.id property rank: + # TODO: add docstring def __get__(self): return self.c.lex.id @@ -126,10 +133,19 @@ cdef class Token: return self.text_with_ws property text: + """A unicode representation of the token text. + + RETURNS (unicode): The original verbatim text of the token. + """ def __get__(self): return self.orth_ property text_with_ws: + """The text content of the token with a trailing whitespace character if + it has one. + + RETURNS (unicode): The text content of the span (with trailing whitespace). + """ def __get__(self): cdef unicode orth = self.vocab.strings[self.c.lex.orth] if self.c.spacy: @@ -184,9 +200,13 @@ cdef class Token: return self.c.lex.suffix property lemma: + """Base form of the word, with no inflectional suffixes. + + RETURNS (uint64): Token lemma. + """ def __get__(self): return self.c.lemma - def __set__(self, int lemma): + def __set__(self, attr_t lemma): self.c.lemma = lemma property pos: @@ -196,62 +216,50 @@ cdef class Token: property tag: def __get__(self): return self.c.tag - def __set__(self, int tag): + def __set__(self, attr_t tag): self.vocab.morphology.assign_tag(self.c, tag) property dep: def __get__(self): return self.c.dep - def __set__(self, int label): + def __set__(self, attr_t label): self.c.dep = label property has_vector: - """ - A boolean value indicating whether a word vector is associated with the object. + """A boolean value indicating whether a word vector is associated with + the object. + + RETURNS (bool): Whether a word vector is associated with the object. """ def __get__(self): if 'has_vector' in self.doc.user_token_hooks: return self.doc.user_token_hooks['has_vector'](self) - cdef int i - for i in range(self.vocab.vectors_length): - if self.c.lex.vector[i] != 0: - return True - else: - return False + return self.vocab.has_vector(self.c.lex.orth) property vector: - """ - A real-valued meaning representation. + """A real-valued meaning representation. - Type: numpy.ndarray[ndim=1, dtype='float32'] + RETURNS (numpy.ndarray[ndim=1, dtype='float32']): A 1D numpy array + representing the token's semantics. """ def __get__(self): if 'vector' in self.doc.user_token_hooks: return self.doc.user_token_hooks['vector'](self) - cdef int length = self.vocab.vectors_length - if length == 0: - raise ValueError( - "Word vectors set to length 0. This may be because you " - "don't have a model installed or loaded, or because your " - "model doesn't include word vectors. For more info, see " - "the documentation: \n%s\n" % about.__docs_models__ - ) - vector_view = self.c.lex.vector - return numpy.asarray(vector_view) - - property repvec: - def __get__(self): - raise AttributeError("repvec was renamed to vector in v0.100") - - property has_repvec: - def __get__(self): - raise AttributeError("has_repvec was renamed to has_vector in v0.100") + if self.has_vector: + return self.vocab.get_vector(self.c.lex.orth) + else: + return self.doc.tensor[self.i] property vector_norm: + """The L2 norm of the token's vector representation. + + RETURNS (float): The L2 norm of the vector representation. + """ def __get__(self): if 'vector_norm' in self.doc.user_token_hooks: return self.doc.user_token_hooks['vector_norm'](self) - return self.c.lex.l2_norm + vector = self.vector + return numpy.sqrt((vector ** 2).sum()) property n_lefts: def __get__(self): @@ -261,6 +269,18 @@ cdef class Token: def __get__(self): return self.c.r_kids + property sent_start: + def __get__(self): + return self.c.sent_start + + def __set__(self, bint value): + if self.doc.is_parsed: + raise ValueError( + 'Refusing to write to token.sent_start if its document is parsed, ' + 'because this may cause inconsistent state. ' + 'See https://github.com/spacy-io/spaCy/issues/235 for workarounds.') + self.c.sent_start = value + property lefts: def __get__(self): """ @@ -324,28 +344,26 @@ cdef class Token: yield from word.subtree property left_edge: - """ - The leftmost token of this token's syntactic descendents. + """The leftmost token of this token's syntactic descendents. - Returns: Token The first token such that self.is_ancestor(token) + RETURNS (Token): The first token such that `self.is_ancestor(token)`. """ def __get__(self): return self.doc[self.c.l_edge] property right_edge: - """ - The rightmost token of this token's syntactic descendents. + """The rightmost token of this token's syntactic descendents. - Returns: Token The last token such that self.is_ancestor(token) + RETURNS (Token): The last token such that `self.is_ancestor(token)`. """ def __get__(self): return self.doc[self.c.r_edge] property ancestors: - """ - A sequence of this token's syntactic ancestors. + """A sequence of this token's syntactic ancestors. - Yields: Token A sequence of ancestor tokens such that ancestor.is_ancestor(self) + YIELDS (Token): A sequence of ancestor tokens such that + `ancestor.is_ancestor(self)`. """ def __get__(self): cdef const TokenC* head_ptr = self.c @@ -357,33 +375,25 @@ cdef class Token: yield self.doc[head_ptr - (self.c - self.i)] i += 1 - def is_ancestor_of(self, descendant): - # TODO: Remove after backward compatibility check. - return self.is_ancestor(descendant) - def is_ancestor(self, descendant): - """ - Check whether this token is a parent, grandparent, etc. of another + """Check whether this token is a parent, grandparent, etc. of another in the dependency tree. - Arguments: - descendant (Token): Another token. - Returns: - is_ancestor (bool): Whether this token is the ancestor of the descendant. + descendant (Token): Another token. + RETURNS (bool): Whether this token is the ancestor of the descendant. """ if self.doc is not descendant.doc: return False return any( ancestor.i == self.i for ancestor in descendant.ancestors ) property head: - """ - The syntactic parent, or "governor", of this token. + """The syntactic parent, or "governor", of this token. - Returns: Token + RETURNS (Token): The token head. """ def __get__(self): - """ - The token predicted by the parser to be the head of the current token. + """The token predicted by the parser to be the head of the current + token. """ return self.doc[self.i + self.c.head] def __set__(self, Token new_head): @@ -399,7 +409,7 @@ cdef class Token: cdef int rel_newhead_i = new_head.i - self.i # is the new head a descendant of the old head - cdef bint is_desc = old_head.is_ancestor_of(new_head) + cdef bint is_desc = old_head.is_ancestor(new_head) cdef int new_edge cdef Token anc, child @@ -477,10 +487,9 @@ cdef class Token: self.c.head = rel_newhead_i property conjuncts: - """ - A sequence of coordinated tokens, including the token itself. + """A sequence of coordinated tokens, including the token itself. - Yields: Token A coordinated token + YIELDS (Token): A coordinated token. """ def __get__(self): """Get a list of conjoined words.""" @@ -495,25 +504,50 @@ cdef class Token: yield from word.conjuncts property ent_type: + """Named entity type. + + RETURNS (uint64): Named entity type. + """ def __get__(self): return self.c.ent_type + def __set__(self, ent_type): + self.c.ent_type = ent_type property ent_iob: + """IOB code of named entity tag. `1="I", 2="O", 3="B"`. 0 means no tag + is assigned. + + RETURNS (uint64): IOB code of named entity tag. + """ def __get__(self): return self.c.ent_iob property ent_type_: + """Named entity type. + + RETURNS (unicode): Named entity type. + """ def __get__(self): return self.vocab.strings[self.c.ent_type] + def __set__(self, ent_type): + self.c.ent_type = self.vocab.strings.add(ent_type) property ent_iob_: + """IOB code of named entity tag. "B" means the token begins an entity, + "I" means it is inside an entity, "O" means it is outside an entity, and + "" means no entity tag is set. + + RETURNS (unicode): IOB code of named entity tag. + """ def __get__(self): iob_strings = ('', 'I', 'O', 'B') return iob_strings[self.c.ent_iob] property ent_id: - """ - An (integer) entity ID. Usually assigned by patterns in the Matcher. + """ID of the entity the token is an instance of, if any. Usually + assigned by patterns in the Matcher. + + RETURNS (uint64): ID of the entity. """ def __get__(self): return self.c.ent_id @@ -522,14 +556,16 @@ cdef class Token: self.c.ent_id = key property ent_id_: - """ - A (string) entity ID. Usually assigned by patterns in the Matcher. + """ID of the entity the token is an instance of, if any. Usually + assigned by patterns in the Matcher. + + RETURNS (unicode): ID of the entity. """ def __get__(self): return self.vocab.strings[self.c.ent_id] def __set__(self, name): - self.c.ent_id = self.vocab.strings[name] + self.c.ent_id = self.vocab.strings.add(name) property whitespace_: def __get__(self): @@ -564,10 +600,14 @@ cdef class Token: return self.vocab.strings[self.c.lex.lang] property lemma_: + """Base form of the word, with no inflectional suffixes. + + RETURNS (unicode): Token lemma. + """ def __get__(self): return self.vocab.strings[self.c.lemma] def __set__(self, unicode lemma_): - self.c.lemma = self.vocab.strings[lemma_] + self.c.lemma = self.vocab.strings.add(lemma_) property pos_: def __get__(self): @@ -577,13 +617,13 @@ cdef class Token: def __get__(self): return self.vocab.strings[self.c.tag] def __set__(self, tag): - self.tag = self.vocab.strings[tag] + self.tag = self.vocab.strings.add(tag) property dep_: def __get__(self): return self.vocab.strings[self.c.dep] def __set__(self, unicode label): - self.c.dep = self.vocab.strings[label] + self.c.dep = self.vocab.strings.add(label) property is_oov: def __get__(self): return Lexeme.c_check_flag(self.c.lex, IS_OOV) diff --git a/spacy/train.py b/spacy/train.py deleted file mode 100644 index 802b13d96..000000000 --- a/spacy/train.py +++ /dev/null @@ -1,104 +0,0 @@ -# coding: utf8 -from __future__ import absolute_import, unicode_literals - -import random -import tqdm -from cytoolz import partition_all - -from thinc.neural.optimizers import Adam -from thinc.neural.ops import NumpyOps, CupyOps -from thinc.neural.train import Trainer as ThincTrainer - -from .syntax.nonproj import PseudoProjectivity -from .gold import GoldParse, merge_sents -from .scorer import Scorer -from .tokens.doc import Doc -from . import util - - -class Trainer(object): - """ - Manage training of an NLP pipeline. - """ - def __init__(self, nlp, gold_tuples, **cfg): - self.nlp = nlp - self.nr_epoch = 0 - self.optimizer = Adam(NumpyOps(), 0.001) - self.gold_tuples = gold_tuples - self.cfg = cfg - self.batch_size = float(util.env_opt('min_batch_size', 4)) - self.max_batch_size = util.env_opt('max_batch_size', 64) - self.accel_batch_size = util.env_opt('batch_accel', 1.001) - - def epochs(self, nr_epoch, augment_data=None, gold_preproc=False): - cached_golds = {} - cached_docs = {} - def _epoch(indices): - all_docs = [] - all_golds = [] - for i in indices: - raw_text, paragraph_tuples = self.gold_tuples[i] - if gold_preproc: - raw_text = None - else: - paragraph_tuples = merge_sents(paragraph_tuples) - if augment_data is None: - if i not in cached_docs: - cached_docs[i] = self.make_docs(raw_text, paragraph_tuples) - docs = cached_docs[i] - if i not in cached_golds: - cached_golds[i] = self.make_golds(docs, paragraph_tuples) - golds = cached_golds[i] - else: - raw_text, paragraph_tuples = augment_data(raw_text, paragraph_tuples) - docs = self.make_docs(raw_text, paragraph_tuples) - golds = self.make_golds(docs, paragraph_tuples) - all_docs.extend(docs) - all_golds.extend(golds) - - thinc_trainer = ThincTrainer(self.nlp.pipeline[0].model) - thinc_trainer.batch_size = int(self.batch_size) - thinc_trainer.nb_epoch = 1 - for X, y in thinc_trainer.iterate(all_docs, all_golds): - yield X, y - thinc_trainer.batch_size = min(int(self.batch_size), self.max_batch_size) - self.batch_size *= self.accel_batch_size - - indices = list(range(len(self.gold_tuples))) - for itn in range(nr_epoch): - random.shuffle(indices) - yield _epoch(indices) - self.nr_epoch += 1 - - def evaluate(self, dev_sents, gold_preproc=False): - all_docs = [] - all_golds = [] - for raw_text, paragraph_tuples in dev_sents: - if gold_preproc: - raw_text = None - else: - paragraph_tuples = merge_sents(paragraph_tuples) - docs = self.make_docs(raw_text, paragraph_tuples) - golds = self.make_golds(docs, paragraph_tuples) - all_docs.extend(docs) - all_golds.extend(golds) - scorer = Scorer() - for doc, gold in zip(self.nlp.pipe(all_docs), all_golds): - scorer.score(doc, gold) - return scorer - - def make_docs(self, raw_text, paragraph_tuples): - if raw_text is not None: - return [self.nlp.make_doc(raw_text)] - else: - return [ - Doc(self.nlp.vocab, words=sent_tuples[0][1]) - for sent_tuples in paragraph_tuples] - - def make_golds(self, docs, paragraph_tuples): - if len(docs) == 1: - return [GoldParse.from_annot_tuples(docs[0], sent_tuples[0]) - for sent_tuples in paragraph_tuples] - else: - return [GoldParse.from_annot_tuples(doc, sent_tuples[0]) - for doc, sent_tuples in zip(docs, paragraph_tuples)] diff --git a/spacy/typedefs.pxd b/spacy/typedefs.pxd index bd863d247..bd5b38958 100644 --- a/spacy/typedefs.pxd +++ b/spacy/typedefs.pxd @@ -4,7 +4,7 @@ from libc.stdint cimport uint8_t ctypedef uint64_t hash_t ctypedef char* utf8_t -ctypedef int32_t attr_t +ctypedef uint64_t attr_t ctypedef uint64_t flags_t ctypedef uint16_t len_t ctypedef uint16_t tag_t diff --git a/spacy/util.py b/spacy/util.py index f481acb5f..ccb81fbed 100644 --- a/spacy/util.py +++ b/spacy/util.py @@ -9,9 +9,20 @@ import regex as re from pathlib import Path import sys import textwrap +import random +import numpy +import io +import dill +from collections import OrderedDict + +import msgpack +import msgpack_numpy +msgpack_numpy.patch() +import ujson from .symbols import ORTH from .compat import cupy, CudaStream, path2str, basestring_, input_, unicode_ +from .compat import copy_array, normalize_string_keys, getattr_ LANGUAGES = {} @@ -77,27 +88,94 @@ def ensure_path(path): return path -def resolve_model_path(name): - """Resolve a model name or string to a model path. +def load_model(name, **overrides): + """Load a model from a shortcut link, package or data path. name (unicode): Package name, shortcut link or model path. - RETURNS (Path): Path to model data directory. + **overrides: Specific overrides, like pipeline components to disable. + RETURNS (Language): `Language` class with the loaded model. """ data_path = get_data_path() if not data_path or not data_path.exists(): raise IOError("Can't find spaCy data path: %s" % path2str(data_path)) if isinstance(name, basestring_): - if (data_path / name).exists(): # in data dir or shortcut link - return (data_path / name) - if is_package(name): # installed as a package - return get_model_package_path(name) - if Path(name).exists(): # path to model - return Path(name) - elif hasattr(name, 'exists'): # Path or Path-like object - return name + if name in set([d.name for d in data_path.iterdir()]): # in data dir / shortcut + return load_model_from_link(name, **overrides) + if is_package(name): # installed as package + return load_model_from_package(name, **overrides) + if Path(name).exists(): # path to model data directory + return load_model_from_path(Path(name), **overrides) + elif hasattr(name, 'exists'): # Path or Path-like to model data + return load_model_from_path(name, **overrides) raise IOError("Can't find model '%s'" % name) +def load_model_from_link(name, **overrides): + """Load a model from a shortcut link, or directory in spaCy data path.""" + init_file = get_data_path() / name / '__init__.py' + spec = importlib.util.spec_from_file_location(name, init_file) + try: + cls = importlib.util.module_from_spec(spec) + except AttributeError: + raise IOError( + "Cant' load '%s'. If you're using a shortcut link, make sure it " + "points to a valid model package (not just a data directory)." % name) + spec.loader.exec_module(cls) + return cls.load(**overrides) + + +def load_model_from_package(name, **overrides): + """Load a model from an installed package.""" + cls = importlib.import_module(name) + return cls.load(**overrides) + + +def load_model_from_path(model_path, meta=False, **overrides): + """Load a model from a data directory path. Creates Language class with + pipeline from meta.json and then calls from_disk() with path.""" + if not meta: + meta = get_model_meta(model_path) + cls = get_lang_class(meta['lang']) + nlp = cls(pipeline=meta.get('pipeline', True), meta=meta, **overrides) + return nlp.from_disk(model_path) + + +def load_model_from_init_py(init_file, **overrides): + """Helper function to use in the `load()` method of a model package's + __init__.py. + + init_file (unicode): Path to model's __init__.py, i.e. `__file__`. + **overrides: Specific overrides, like pipeline components to disable. + RETURNS (Language): `Language` class with loaded model. + """ + model_path = Path(init_file).parent + meta = get_model_meta(model_path) + data_dir = '%s_%s-%s' % (meta['lang'], meta['name'], meta['version']) + data_path = model_path / data_dir + if not model_path.exists(): + raise ValueError("Can't find model directory: %s" % path2str(data_path)) + return load_model_from_path(data_path, meta, **overrides) + + +def get_model_meta(path): + """Get model meta.json from a directory path and validate its contents. + + path (unicode or Path): Path to model directory. + RETURNS (dict): The model's meta data. + """ + model_path = ensure_path(path) + if not model_path.exists(): + raise ValueError("Can't find model directory: %s" % path2str(model_path)) + meta_path = model_path / 'meta.json' + if not meta_path.is_file(): + raise IOError("Could not read meta.json from %s" % meta_path) + meta = read_json(meta_path) + for setting in ['lang', 'name', 'version']: + if setting not in meta: + raise ValueError('No %s setting found in model meta.json' % setting) + return meta + + def is_package(name): """Check if string maps to a package installed via pip. @@ -111,40 +189,21 @@ def is_package(name): return False -def get_model_package_path(package_name): - """Get path to a model package installed via pip. +def get_package_path(name): + """Get the path to an installed package. - package_name (unicode): Name of installed package. - RETURNS (Path): Path to model data directory. + name (unicode): Package name. + RETURNS (Path): Path to installed package. """ # Here we're importing the module just to find it. This is worryingly # indirect, but it's otherwise very difficult to find the package. - # Python's installation and import rules are very complicated. - pkg = importlib.import_module(package_name) - package_path = Path(pkg.__file__).parent.parent - meta = parse_package_meta(package_path / package_name) - model_name = '%s-%s' % (package_name, meta['version']) - return package_path / package_name / model_name - - -def parse_package_meta(package_path, require=True): - """Check if a meta.json exists in a package and return its contents. - - package_path (Path): Path to model package directory. - require (bool): If True, raise error if no meta.json is found. - RETURNS (dict or None): Model meta.json data or None. - """ - location = package_path / 'meta.json' - if location.is_file(): - return read_json(location) - elif require: - raise IOError("Could not read meta.json from %s" % location) - else: - return None + pkg = importlib.import_module(name) + return Path(pkg.__file__).parent def is_in_jupyter(): - """Check if user is in a Jupyter notebook. Mainly used for displaCy. + """Check if user is running spaCy from a Jupyter notebook by detecting the + IPython kernel. Mainly used for the displaCy visualizer. RETURNS (bool): True if in Jupyter, False if not. """ @@ -173,6 +232,41 @@ def get_async(stream, numpy_array): return array +def itershuffle(iterable, bufsize=1000): + """Shuffle an iterator. This works by holding `bufsize` items back + and yielding them sometime later. Obviously, this is not unbiased – + but should be good enough for batching. Larger bufsize means less bias. + From https://gist.github.com/andres-erbsen/1307752 + + iterable (iterable): Iterator to shuffle. + bufsize (int): Items to hold back. + YIELDS (iterable): The shuffled iterator. + """ + iterable = iter(iterable) + buf = [] + try: + while True: + for i in range(random.randint(1, bufsize-len(buf))): + buf.append(iterable.next()) + random.shuffle(buf) + for i in range(random.randint(1, bufsize)): + if buf: + yield buf.pop() + else: + break + except StopIteration: + random.shuffle(buf) + while buf: + yield buf.pop() + raise StopIteration + + +_PRINT_ENV = False +def set_env_log(value): + global _PRINT_ENV + _PRINT_ENV = value + + def env_opt(name, default=None): if type(default) is float: type_convert = float @@ -180,14 +274,17 @@ def env_opt(name, default=None): type_convert = int if 'SPACY_' + name.upper() in os.environ: value = type_convert(os.environ['SPACY_' + name.upper()]) - print(name, "=", repr(value), "via", "$SPACY_" + name.upper()) + if _PRINT_ENV: + print(name, "=", repr(value), "via", "$SPACY_" + name.upper()) return value elif name in os.environ: value = type_convert(os.environ[name]) - print(name, "=", repr(value), "via", '$' + name) + if _PRINT_ENV: + print(name, "=", repr(value), "via", '$' + name) return value else: - print(name, '=', repr(default), "by default") + if _PRINT_ENV: + print(name, '=', repr(default), "by default") return default @@ -219,6 +316,22 @@ def compile_infix_regex(entries): return re.compile(expression) +def add_lookups(default_func, *lookups): + """Extend an attribute function with special cases. If a word is in the + lookups, the value is returned. Otherwise the previous function is used. + + default_func (callable): The default function to execute. + *lookups (dict): Lookup dictionary mapping string to attribute value. + RETURNS (callable): Lexical attribute getter. + """ + def get_attr(string): + for lookup in lookups: + if string in lookup: + return lookup[string] + return default_func(string) + return get_attr + + def update_exc(base_exceptions, *addition_dicts): """Update and validate tokenizer exceptions. Will overwrite exceptions. @@ -286,10 +399,33 @@ def normalize_slice(length, start, stop, step=None): return start, stop -def check_renamed_kwargs(renamed, kwargs): - for old, new in renamed.items(): - if old in kwargs: - raise TypeError("Keyword argument %s now renamed to %s" % (old, new)) +def compounding(start, stop, compound): + """Yield an infinite series of compounding values. Each time the + generator is called, a value is produced by multiplying the previous + value by the compound rate. + + EXAMPLE: + >>> sizes = compounding(1., 10., 1.5) + >>> assert next(sizes) == 1. + >>> assert next(sizes) == 1 * 1.5 + >>> assert next(sizes) == 1.5 * 1.5 + """ + def clip(value): + return max(value, stop) if (start>stop) else min(value, stop) + curr = float(start) + while True: + yield clip(curr) + curr *= compound + + +def decaying(start, stop, decay): + """Yield an infinite series of linearly decaying values.""" + def clip(value): + return max(value, stop) if (start>stop) else min(value, stop) + nr_upd = 1. + while True: + yield clip(start * 1./(1. + decay * nr_upd)) + nr_upd += 1 def read_json(location): @@ -298,6 +434,7 @@ def read_json(location): location (Path): Path to JSON file. RETURNS (dict): Loaded JSON content. """ + location = ensure_path(location) with location.open('r', encoding='utf8') as f: return ujson.load(f) @@ -315,6 +452,40 @@ def get_raw_input(description, default=False): return user_input +def to_bytes(getters, exclude): + serialized = OrderedDict() + for key, getter in getters.items(): + if key not in exclude: + serialized[key] = getter() + return msgpack.dumps(serialized, use_bin_type=True, encoding='utf8') + + +def from_bytes(bytes_data, setters, exclude): + msg = msgpack.loads(bytes_data, encoding='utf8') + for key, setter in setters.items(): + if key not in exclude and key in msg: + setter(msg[key]) + return msg + + +def to_disk(path, writers, exclude): + path = ensure_path(path) + if not path.exists(): + path.mkdir() + for key, writer in writers.items(): + if key not in exclude: + writer(path / key) + return path + + +def from_disk(path, readers, exclude): + path = ensure_path(path) + for key, reader in readers.items(): + if key not in exclude: + reader(path / key) + return path + + def print_table(data, title=None): """Print data in table format. @@ -324,7 +495,7 @@ def print_table(data, title=None): if isinstance(data, dict): data = list(data.items()) tpl_row = ' {:<15}' * len(data[0]) - table = '\n'.join([tpl_row.format(l, v) for l, v in data]) + table = '\n'.join([tpl_row.format(l, unicode_(v)) for l, v in data]) if title: print('\n \033[93m{}\033[0m'.format(title)) print('\n{}\n'.format(table)) @@ -337,11 +508,12 @@ def print_markdown(data, title=None): title (unicode or None): Title, will be rendered as headline 2. """ def excl_value(value): - return Path(value).exists() # contains path (personal info) + # contains path, i.e. personal info + return isinstance(value, basestring_) and Path(value).exists() if isinstance(data, dict): data = list(data.items()) - markdown = ["* **{}:** {}".format(l, v) for l, v in data if not excl_value(v)] + markdown = ["* **{}:** {}".format(l, unicode_(v)) for l, v in data if not excl_value(v)] if title: print("\n## {}".format(title)) print('\n{}\n'.format('\n'.join(markdown))) @@ -353,13 +525,13 @@ def prints(*texts, **kwargs): *texts (unicode): Texts to print. Each argument is rendered as paragraph. **kwargs: 'title' becomes coloured headline. 'exits'=True performs sys exit. """ - exits = kwargs.get('exits', False) + exits = kwargs.get('exits', None) title = kwargs.get('title', None) title = '\033[93m{}\033[0m\n'.format(_wrap(title)) if title else '' message = '\n\n'.join([_wrap(text) for text in texts]) print('\n{}{}\n'.format(title, message)) - if exits: - sys.exit(0) + if exits is not None: + sys.exit(exits) def _wrap(text, wrap_max=80, indent=4): diff --git a/spacy/vectors.pyx b/spacy/vectors.pyx new file mode 100644 index 000000000..35d4d17ab --- /dev/null +++ b/spacy/vectors.pyx @@ -0,0 +1,94 @@ +import numpy +from collections import OrderedDict +import msgpack +import msgpack_numpy +msgpack_numpy.patch() + +from .strings cimport StringStore +from . import util + + +cdef class Vectors: + '''Store, save and load word vectors.''' + cdef public object data + cdef readonly StringStore strings + cdef public object key2i + + def __init__(self, strings, data_or_width): + self.strings = StringStore() + if isinstance(data_or_width, int): + self.data = data = numpy.zeros((len(strings), data_or_width), + dtype='f') + else: + data = data_or_width + self.data = data + self.key2i = {} + for i, string in enumerate(strings): + self.key2i[self.strings.add(string)] = i + + def __reduce__(self): + return (Vectors, (self.strings, self.data)) + + def __getitem__(self, key): + if isinstance(key, basestring): + key = self.strings[key] + i = self.key2i[key] + if i is None: + raise KeyError(key) + else: + return self.data[i] + + def __setitem__(self, key, vector): + if isinstance(key, basestring): + key = self.strings.add(key) + i = self.key2i[key] + self.data[i] = vector + + def __iter__(self): + yield from self.data + + def __len__(self): + return len(self.strings) + + def items(self): + for i, string in enumerate(self.strings): + yield string, self.data[i] + + @property + def shape(self): + return self.data.shape + + def most_similar(self, key): + raise NotImplementedError + + def to_disk(self, path): + raise NotImplementedError + + def from_disk(self, path): + raise NotImplementedError + + def to_bytes(self, **exclude): + def serialize_weights(): + if hasattr(self.weights, 'to_bytes'): + return self.weights.to_bytes() + else: + return msgpack.dumps(self.weights) + + serializers = OrderedDict(( + ('strings', lambda: self.strings.to_bytes()), + ('weights', serialize_weights) + )) + return util.to_bytes(serializers, exclude) + + def from_bytes(self, data, **exclude): + def deserialize_weights(b): + if hasattr(self.weights, 'from_bytes'): + self.weights.from_bytes() + else: + self.weights = msgpack.loads(b) + + deserializers = OrderedDict(( + ('strings', lambda b: self.strings.from_bytes(b)), + ('weights', deserialize_weights) + )) + return util.from_bytes(deserializers, exclude) diff --git a/spacy/vocab.pxd b/spacy/vocab.pxd index 3c31a8f8f..8005cbf06 100644 --- a/spacy/vocab.pxd +++ b/spacy/vocab.pxd @@ -27,7 +27,8 @@ cdef struct _Cached: cdef class Vocab: cdef Pool mem cpdef readonly StringStore strings - cpdef readonly Morphology morphology + cpdef public Morphology morphology + cpdef public object vectors cdef readonly int length cdef public object data_dir cdef public object lex_attr_getters @@ -35,11 +36,10 @@ cdef class Vocab: cdef const LexemeC* get(self, Pool mem, unicode string) except NULL cdef const LexemeC* get_by_orth(self, Pool mem, attr_t orth) except NULL cdef const TokenC* make_fused_token(self, substrings) except NULL - + cdef const LexemeC* _new_lexeme(self, Pool mem, unicode string) except NULL cdef int _add_lex_to_vocab(self, hash_t key, const LexemeC* lex) except -1 cdef const LexemeC* _new_lexeme(self, Pool mem, unicode string) except NULL cdef PreshMap _by_hash cdef PreshMap _by_orth - cdef readonly int vectors_length diff --git a/spacy/vocab.pyx b/spacy/vocab.pyx index 0a68c6ef3..149317779 100644 --- a/spacy/vocab.pyx +++ b/spacy/vocab.pyx @@ -9,6 +9,7 @@ from libc.string cimport memset, memcpy from libc.stdint cimport int32_t from libc.math cimport sqrt from cymem.cymem cimport Address +from collections import OrderedDict from .lexeme cimport EMPTY_LEXEME from .lexeme cimport Lexeme from .strings cimport hash_string @@ -26,92 +27,24 @@ from . import attrs from . import symbols -DEF MAX_VEC_SIZE = 100000 - - -cdef float[MAX_VEC_SIZE] EMPTY_VEC -memset(EMPTY_VEC, 0, sizeof(EMPTY_VEC)) -memset(&EMPTY_LEXEME, 0, sizeof(LexemeC)) -EMPTY_LEXEME.vector = EMPTY_VEC - - cdef class Vocab: + """A look-up table that allows you to access `Lexeme` objects. The `Vocab` + instance also provides access to the `StringStore`, and owns underlying + C-data that is shared between `Doc` objects. """ - A map container for a language's LexemeC structs. - """ - @classmethod - def load(cls, path, lex_attr_getters=None, lemmatizer=True, - tag_map=True, oov_prob=True, **deprecated_kwargs): - """ - Deprecated --- replace in spaCy 2 - Load the vocabulary from a path. - - Arguments: - path (Path): - The path to load from. - lex_attr_getters (dict): - A dictionary mapping attribute IDs to functions to compute them. - Defaults to None. - lemmatizer (object): - A lemmatizer. Defaults to None. - tag_map (dict): - A dictionary mapping fine-grained tags to coarse-grained parts-of-speech, - and optionally morphological attributes. - oov_prob (float): - The default probability for out-of-vocabulary words. - Returns: - Vocab: The newly constructed vocab object. - """ - path = util.ensure_path(path) - util.check_renamed_kwargs({'get_lex_attr': 'lex_attr_getters'}, deprecated_kwargs) - if 'vectors' in deprecated_kwargs: - raise AttributeError( - "vectors argument to Vocab.load() deprecated. " - "Install vectors after loading.") - if tag_map is True and (path / 'vocab' / 'tag_map.json').exists(): - with (path / 'vocab' / 'tag_map.json').open('r', encoding='utf8') as file_: - tag_map = ujson.load(file_) - elif tag_map is True: - tag_map = None - if lex_attr_getters is not None \ - and oov_prob is True \ - and (path / 'vocab' / 'oov_prob').exists(): - with (path / 'vocab' / 'oov_prob').open('r', encoding='utf8') as file_: - oov_prob = float(file_.read()) - lex_attr_getters[PROB] = lambda text: oov_prob - if lemmatizer is True: - lemmatizer = Lemmatizer.load(path) - - with (path / 'vocab' / 'strings.json').open('r', encoding='utf8') as file_: - strings_list = ujson.load(file_) - cdef Vocab self = cls(lex_attr_getters=lex_attr_getters, tag_map=tag_map, - lemmatizer=lemmatizer, - strings=strings_list) - self.load_lexemes(path / 'vocab' / 'lexemes.bin') - return self - - def __init__(self, lex_attr_getters=None, tag_map=None, lemmatizer=None, strings=tuple(), **deprecated_kwargs): + """Create the vocabulary. + + lex_attr_getters (dict): A dictionary mapping attribute IDs to functions + to compute them. Defaults to `None`. + tag_map (dict): A dictionary mapping fine-grained tags to coarse-grained + parts-of-speech, and optionally morphological attributes. + lemmatizer (object): A lemmatizer. Defaults to `None`. + strings (StringStore): StringStore that maps strings to integers, and + vice versa. + RETURNS (Vocab): The newly constructed vocab object. """ - Create the vocabulary. - - lex_attr_getters (dict): - A dictionary mapping attribute IDs to functions to compute them. - Defaults to None. - lemmatizer (object): - A lemmatizer. Defaults to None. - tag_map (dict): - A dictionary mapping fine-grained tags to coarse-grained parts-of-speech, - and optionally morphological attributes. - oov_prob (float): - The default probability for out-of-vocabulary words. - - Returns: - Vocab: The newly constructed vocab object. - """ - util.check_renamed_kwargs({'get_lex_attr': 'lex_attr_getters'}, deprecated_kwargs) - lex_attr_getters = lex_attr_getters if lex_attr_getters is not None else {} tag_map = tag_map if tag_map is not None else {} if lemmatizer in (None, True, False): @@ -121,25 +54,16 @@ cdef class Vocab: self._by_hash = PreshMap() self._by_orth = PreshMap() self.strings = StringStore() + self.length = 0 if strings: for string in strings: - self.strings[string] - # Load strings in a special order, so that we have an onset number for - # the vocabulary. This way, when words are added in order, the orth ID - # is the frequency rank of the word, plus a certain offset. The structural - # strings are loaded first, because the vocab is open-class, and these - # symbols are closed class. - # TODO: Actually this has turned out to be a pain in the ass... - # It means the data is invalidated when we add a symbol :( - # Need to rethink this. - for name in symbols.NAMES + list(sorted(tag_map.keys())): + _ = self[string] + for name in tag_map.keys(): if name: - _ = self.strings[name] + self.strings.add(name) self.lex_attr_getters = lex_attr_getters self.morphology = Morphology(self.strings, tag_map, lemmatizer) - self.length = 1 - property lang: def __get__(self): langfunc = None @@ -148,33 +72,32 @@ cdef class Vocab: return langfunc('_') if langfunc else '' def __len__(self): - """ - The current number of lexemes stored. + """The current number of lexemes stored. + + RETURNS (int): The current number of lexemes stored. """ return self.length - - def add_flag(self, flag_getter, int flag_id=-1): - """ - Set a new boolean flag to words in the vocabulary. - The flag_setter function will be called over the words currently in the + def add_flag(self, flag_getter, int flag_id=-1): + """Set a new boolean flag to words in the vocabulary. + + The flag_getter function will be called over the words currently in the vocab, and then applied to new words as they occur. You'll then be able to access the flag value on each token, using token.check_flag(flag_id). + See also: `Lexeme.set_flag`, `Lexeme.check_flag`, `Token.set_flag`, + `Token.check_flag`. - See also: - Lexeme.set_flag, Lexeme.check_flag, Token.set_flag, Token.check_flag. + flag_getter (callable): A function `f(unicode) -> bool`, to get the flag + value. + flag_id (int): An integer between 1 and 63 (inclusive), specifying + the bit at which the flag will be stored. If -1, the lowest + available bit will be chosen. + RETURNS (int): The integer ID by which the flag value can be checked. - Arguments: - flag_getter: - A function f(unicode) -> bool, to get the flag value. - - flag_id (int): - An integer between 1 and 63 (inclusive), specifying the bit at which the - flag will be stored. If -1, the lowest available bit will be - chosen. - - Returns: - flag_id (int): The integer ID by which the flag value can be checked. + EXAMPLE: + >>> MY_PRODUCT = nlp.vocab.add_flag(lambda text: text in ['spaCy', 'dislaCy']) + >>> doc = nlp(u'I like spaCy') + >>> assert doc[2].check_flag(MY_PRODUCT) == True """ if flag_id == -1: for bit in range(1, 64): @@ -196,9 +119,8 @@ cdef class Vocab: return flag_id cdef const LexemeC* get(self, Pool mem, unicode string) except NULL: - """ - Get a pointer to a LexemeC from the lexicon, creating a new Lexeme - if necessary, using memory acquired from the given pool. If the pool + """Get a pointer to a `LexemeC` from the lexicon, creating a new `Lexeme` + if necessary, using memory acquired from the given pool. If the pool is the lexicon's own memory, the lexeme is saved in the lexicon. """ if string == u'': @@ -216,9 +138,8 @@ cdef class Vocab: return self._new_lexeme(mem, string) cdef const LexemeC* get_by_orth(self, Pool mem, attr_t orth) except NULL: - """ - Get a pointer to a LexemeC from the lexicon, creating a new Lexeme - if necessary, using memory acquired from the given pool. If the pool + """Get a pointer to a `LexemeC` from the lexicon, creating a new `Lexeme` + if necessary, using memory acquired from the given pool. If the pool is the lexicon's own memory, the lexeme is saved in the lexicon. """ if orth == 0: @@ -236,15 +157,14 @@ cdef class Vocab: mem = self.mem cdef bint is_oov = mem is not self.mem lex = mem.alloc(sizeof(LexemeC), 1) - lex.orth = self.strings[string] + lex.orth = self.strings.add(string) lex.length = len(string) lex.id = self.length - lex.vector = mem.alloc(self.vectors_length, sizeof(float)) if self.lex_attr_getters is not None: for attr, func in self.lex_attr_getters.items(): value = func(string) if isinstance(value, unicode): - value = self.strings[value] + value = self.strings.add(value) if attr == PROB: lex.prob = value elif value is not None: @@ -263,24 +183,19 @@ cdef class Vocab: self.length += 1 def __contains__(self, unicode string): - """ - Check whether the string has an entry in the vocabulary. + """Check whether the string has an entry in the vocabulary. - Arguments: - string (unicode): The ID string. - - Returns: - bool Whether the string has an entry in the vocabulary. + string (unicode): The ID string. + RETURNS (bool) Whether the string has an entry in the vocabulary. """ key = hash_string(string) lex = self._by_hash.get(key) return lex is not NULL def __iter__(self): - """ - Iterate over the lexemes in the vocabulary. + """Iterate over the lexemes in the vocabulary. - Yields: Lexeme An entry in the vocabulary. + YIELDS (Lexeme): An entry in the vocabulary. """ cdef attr_t orth cdef size_t addr @@ -288,23 +203,23 @@ cdef class Vocab: yield Lexeme(self, orth) def __getitem__(self, id_or_string): - """ - Retrieve a lexeme, given an int ID or a unicode string. If a previously - unseen unicode string is given, a new lexeme is created and stored. + """Retrieve a lexeme, given an int ID or a unicode string. If a + previously unseen unicode string is given, a new lexeme is created and + stored. - Arguments: - id_or_string (int or unicode): - The integer ID of a word, or its unicode string. + id_or_string (int or unicode): The integer ID of a word, or its unicode + string. If `int >= Lexicon.size`, `IndexError` is raised. If + `id_or_string` is neither an int nor a unicode string, `ValueError` + is raised. + RETURNS (Lexeme): The lexeme indicated by the given ID. - If an int >= Lexicon.size, IndexError is raised. If id_or_string - is neither an int nor a unicode string, ValueError is raised. - - Returns: - lexeme (Lexeme): The lexeme indicated by the given ID. + EXAMPLE: + >>> apple = nlp.vocab.strings['apple'] + >>> assert nlp.vocab[apple] == nlp.vocab[u'apple'] """ cdef attr_t orth if type(id_or_string) == unicode: - orth = self.strings[id_or_string] + orth = self.strings.add(id_or_string) else: orth = id_or_string return Lexeme(self, orth) @@ -316,31 +231,109 @@ cdef class Vocab: props = intify_attrs(props, strings_map=self.strings, _do_deprecated=True) token = &tokens[i] # Set the special tokens up to have arbitrary attributes - token.lex = self.get_by_orth(self.mem, props[attrs.ORTH]) + lex = self.get_by_orth(self.mem, props[attrs.ORTH]) + token.lex = lex if attrs.TAG in props: self.morphology.assign_tag(token, props[attrs.TAG]) for attr_id, value in props.items(): Token.set_struct_attr(token, attr_id, value) + Lexeme.set_struct_attr(lex, attr_id, value) return tokens + @property + def vectors_length(self): + raise NotImplementedError + + def clear_vectors(self): + """Drop the current vector table. Because all vectors must be the same + width, you have to call this to change the size of the vectors. + """ + raise NotImplementedError + + def get_vector(self, orth): + """Retrieve a vector for a word in the vocabulary. + + Words can be looked up by string or int ID. + + RETURNS: + A word vector. Size and shape determed by the + vocab.vectors instance. Usually, a numpy ndarray + of shape (300,) and dtype float32. + + RAISES: If no vectors data is loaded, ValueError is raised. + """ + raise NotImplementedError + + def set_vector(self, orth, vector): + """Set a vector for a word in the vocabulary. + + Words can be referenced by string or int ID. + + RETURNS: + None + """ + raise NotImplementedError + + def has_vector(self, orth): + """Check whether a word has a vector. Returns False if no + vectors have been loaded. Words can be looked up by string + or int ID.""" + return False + def to_disk(self, path): + """Save the current state to a directory. + + path (unicode or Path): A path to a directory, which will be created if + it doesn't exist. Paths may be either strings or `Path`-like objects. + """ path = util.ensure_path(path) if not path.exists(): path.mkdir() - strings_loc = path / 'strings.json' - with strings_loc.open('w', encoding='utf8') as file_: - self.strings.dump(file_) - self.dump(path / 'lexemes.bin') + self.strings.to_disk(path / 'strings.json') + with (path / 'lexemes.bin').open('wb') as file_: + file_.write(self.lexemes_to_bytes()) def from_disk(self, path): - path = util.ensure_path(path) - with (path / 'vocab' / 'strings.json').open('r', encoding='utf8') as file_: - strings_list = ujson.load(file_) - for string in strings_list: - self.strings[string] - self.load_lexemes(path / 'lexemes.bin') + """Loads state from a directory. Modifies the object in place and + returns it. - def lexemes_to_bytes(self, **exclude): + path (unicode or Path): A path to a directory. Paths may be either + strings or `Path`-like objects. + RETURNS (Vocab): The modified `Vocab` object. + """ + path = util.ensure_path(path) + self.strings.from_disk(path / 'strings.json') + with (path / 'lexemes.bin').open('rb') as file_: + self.lexemes_from_bytes(file_.read()) + return self + + def to_bytes(self, **exclude): + """Serialize the current state to a binary string. + + **exclude: Named attributes to prevent from being serialized. + RETURNS (bytes): The serialized form of the `Vocab` object. + """ + getters = OrderedDict(( + ('strings', lambda: self.strings.to_bytes()), + ('lexemes', lambda: self.lexemes_to_bytes()), + )) + return util.to_bytes(getters, exclude) + + def from_bytes(self, bytes_data, **exclude): + """Load state from a binary string. + + bytes_data (bytes): The data to load from. + **exclude: Named attributes to prevent from being loaded. + RETURNS (Vocab): The `Vocab` object. + """ + setters = OrderedDict(( + ('strings', lambda b: self.strings.from_bytes(b)), + ('lexemes', lambda b: self.lexemes_from_bytes(b)), + )) + util.from_bytes(bytes_data, setters, exclude) + return self + + def lexemes_to_bytes(self): cdef hash_t key cdef size_t addr cdef LexemeC* lexeme = NULL @@ -365,9 +358,7 @@ cdef class Vocab: return byte_string def lexemes_from_bytes(self, bytes bytes_data): - """ - Load the binary vocabulary data from the given string. - """ + """Load the binary vocabulary data from the given string.""" cdef LexemeC* lexeme cdef hash_t key cdef unicode py_str @@ -382,7 +373,6 @@ cdef class Vocab: lex_data.data[j] = bytes_ptr[i+j] Lexeme.c_from_bytes(lexeme, lex_data) - lexeme.vector = EMPTY_VEC py_str = self.strings[lexeme.orth] assert self.strings[py_str] == lexeme.orth, (py_str, lexeme.orth) key = hash_string(py_str) @@ -390,184 +380,6 @@ cdef class Vocab: self._by_orth.set(lexeme.orth, lexeme) self.length += 1 - # Deprecated --- delete these once stable - - def dump_vectors(self, out_loc): - """ - Save the word vectors to a binary file. - - Arguments: - loc (Path): The path to save to. - Returns: - None - #""" - cdef int32_t vec_len = self.vectors_length - cdef int32_t word_len - cdef bytes word_str - cdef char* chars - - cdef Lexeme lexeme - cdef CFile out_file = CFile(out_loc, 'wb') - for lexeme in self: - word_str = lexeme.orth_.encode('utf8') - vec = lexeme.c.vector - word_len = len(word_str) - - out_file.write_from(&word_len, 1, sizeof(word_len)) - out_file.write_from(&vec_len, 1, sizeof(vec_len)) - - chars = word_str - out_file.write_from(chars, word_len, sizeof(char)) - out_file.write_from(vec, vec_len, sizeof(float)) - out_file.close() - - - - def load_vectors(self, file_): - """ - Load vectors from a text-based file. - - Arguments: - file_ (buffer): The file to read from. Entries should be separated by newlines, - and each entry should be whitespace delimited. The first value of the entry - should be the word string, and subsequent entries should be the values of the - vector. - - Returns: - vec_len (int): The length of the vectors loaded. - """ - cdef LexemeC* lexeme - cdef attr_t orth - cdef int32_t vec_len = -1 - cdef double norm = 0.0 - - whitespace_pattern = re.compile(r'\s', re.UNICODE) - - for line_num, line in enumerate(file_): - pieces = line.split() - word_str = " " if whitespace_pattern.match(line) else pieces.pop(0) - if vec_len == -1: - vec_len = len(pieces) - elif vec_len != len(pieces): - raise VectorReadError.mismatched_sizes(file_, line_num, - vec_len, len(pieces)) - orth = self.strings[word_str] - lexeme = self.get_by_orth(self.mem, orth) - lexeme.vector = self.mem.alloc(vec_len, sizeof(float)) - for i, val_str in enumerate(pieces): - lexeme.vector[i] = float(val_str) - norm = 0.0 - for i in range(vec_len): - norm += lexeme.vector[i] * lexeme.vector[i] - lexeme.l2_norm = sqrt(norm) - self.vectors_length = vec_len - return vec_len - - def load_vectors_from_bin_loc(self, loc): - """ - Load vectors from the location of a binary file. - - Arguments: - loc (unicode): The path of the binary file to load from. - - Returns: - vec_len (int): The length of the vectors loaded. - """ - cdef CFile file_ = CFile(loc, b'rb') - cdef int32_t word_len - cdef int32_t vec_len = 0 - cdef int32_t prev_vec_len = 0 - cdef float* vec - cdef Address mem - cdef attr_t string_id - cdef bytes py_word - cdef vector[float*] vectors - cdef int line_num = 0 - cdef Pool tmp_mem = Pool() - while True: - try: - file_.read_into(&word_len, sizeof(word_len), 1) - except IOError: - break - file_.read_into(&vec_len, sizeof(vec_len), 1) - if prev_vec_len != 0 and vec_len != prev_vec_len: - raise VectorReadError.mismatched_sizes(loc, line_num, - vec_len, prev_vec_len) - if 0 >= vec_len >= MAX_VEC_SIZE: - raise VectorReadError.bad_size(loc, vec_len) - - chars = file_.alloc_read(tmp_mem, word_len, sizeof(char)) - vec = file_.alloc_read(self.mem, vec_len, sizeof(float)) - - string_id = self.strings[chars[:word_len]] - # Insert words into vocab to add vector. - self.get_by_orth(self.mem, string_id) - while string_id >= vectors.size(): - vectors.push_back(EMPTY_VEC) - assert vec != NULL - vectors[string_id] = vec - line_num += 1 - cdef LexemeC* lex - cdef size_t lex_addr - cdef double norm = 0.0 - cdef int i - for orth, lex_addr in self._by_orth.items(): - lex = lex_addr - if lex.lower < vectors.size(): - lex.vector = vectors[lex.lower] - norm = 0.0 - for i in range(vec_len): - norm += lex.vector[i] * lex.vector[i] - lex.l2_norm = sqrt(norm) - else: - lex.vector = EMPTY_VEC - self.vectors_length = vec_len - return vec_len - - - def resize_vectors(self, int new_size): - """ - Set vectors_length to a new size, and allocate more memory for the Lexeme - vectors if necessary. The memory will be zeroed. - - Arguments: - new_size (int): The new size of the vectors. - """ - cdef hash_t key - cdef size_t addr - if new_size > self.vectors_length: - for key, addr in self._by_hash.items(): - lex = addr - lex.vector = self.mem.realloc(lex.vector, - new_size * sizeof(lex.vector[0])) - self.vectors_length = new_size - - -def write_binary_vectors(in_loc, out_loc): - cdef CFile out_file = CFile(out_loc, 'wb') - cdef Address mem - cdef int32_t word_len - cdef int32_t vec_len - cdef char* chars - with bz2.BZ2File(in_loc, 'r') as file_: - for line in file_: - pieces = line.split() - word = pieces.pop(0) - mem = Address(len(pieces), sizeof(float)) - vec = mem.ptr - for i, val_str in enumerate(pieces): - vec[i] = float(val_str) - - word_len = len(word) - vec_len = len(pieces) - - out_file.write_from(&word_len, 1, sizeof(word_len)) - out_file.write_from(&vec_len, 1, sizeof(vec_len)) - - chars = word - out_file.write_from(chars, len(word), sizeof(char)) - out_file.write_from(vec, vec_len, sizeof(float)) - def pickle_vocab(vocab): sstore = vocab.strings @@ -577,25 +389,22 @@ def pickle_vocab(vocab): lex_attr_getters = vocab.lex_attr_getters lexemes_data = vocab.lexemes_to_bytes() - vectors_length = vocab.vectors_length return (unpickle_vocab, (sstore, morph, data_dir, lex_attr_getters, - lexemes_data, length, vectors_length)) + lexemes_data, length)) def unpickle_vocab(sstore, morphology, data_dir, - lex_attr_getters, bytes lexemes_data, int length, int vectors_length): + lex_attr_getters, bytes lexemes_data, int length): cdef Vocab vocab = Vocab() vocab.length = length - vocab.vectors_length = vectors_length vocab.strings = sstore vocab.morphology = morphology vocab.data_dir = data_dir vocab.lex_attr_getters = lex_attr_getters vocab.lexemes_from_bytes(lexemes_data) vocab.length = length - vocab.vectors_length = vectors_length return vocab @@ -615,255 +424,3 @@ class LookupError(Exception): "ID of orth: {orth_id}".format( query=repr(original_string), orth_str=repr(id_string), orth_id=id_) ) - - -class VectorReadError(Exception): - @classmethod - def mismatched_sizes(cls, loc, line_num, prev_size, curr_size): - return cls( - "Error reading word vectors from %s on line %d.\n" - "All vectors must be the same size.\n" - "Prev size: %d\n" - "Curr size: %d" % (loc, line_num, prev_size, curr_size)) - - @classmethod - def bad_size(cls, loc, size): - return cls( - "Error reading word vectors from %s.\n" - "Vector size: %d\n" - "Max size: %d\n" - "Min size: 1\n" % (loc, size, MAX_VEC_SIZE)) - - -# -#Deprecated --- delete these once stable -# -# def dump_vectors(self, out_loc): -# """ -# Save the word vectors to a binary file. -# -# Arguments: -# loc (Path): The path to save to. -# Returns: -# None -# #""" -# cdef int32_t vec_len = self.vectors_length -# cdef int32_t word_len -# cdef bytes word_str -# cdef char* chars -# -# cdef Lexeme lexeme -# cdef CFile out_file = CFile(out_loc, 'wb') -# for lexeme in self: -# word_str = lexeme.orth_.encode('utf8') -# vec = lexeme.c.vector -# word_len = len(word_str) -# -# out_file.write_from(&word_len, 1, sizeof(word_len)) -# out_file.write_from(&vec_len, 1, sizeof(vec_len)) -# -# chars = word_str -# out_file.write_from(chars, word_len, sizeof(char)) -# out_file.write_from(vec, vec_len, sizeof(float)) -# out_file.close() -# -# -# -# def load_vectors(self, file_): -# """ -# Load vectors from a text-based file. -# -# Arguments: -# file_ (buffer): The file to read from. Entries should be separated by newlines, -# and each entry should be whitespace delimited. The first value of the entry -# should be the word string, and subsequent entries should be the values of the -# vector. -# -# Returns: -# vec_len (int): The length of the vectors loaded. -# """ -# cdef LexemeC* lexeme -# cdef attr_t orth -# cdef int32_t vec_len = -1 -# cdef double norm = 0.0 -# -# whitespace_pattern = re.compile(r'\s', re.UNICODE) -# -# for line_num, line in enumerate(file_): -# pieces = line.split() -# word_str = " " if whitespace_pattern.match(line) else pieces.pop(0) -# if vec_len == -1: -# vec_len = len(pieces) -# elif vec_len != len(pieces): -# raise VectorReadError.mismatched_sizes(file_, line_num, -# vec_len, len(pieces)) -# orth = self.strings[word_str] -# lexeme = self.get_by_orth(self.mem, orth) -# lexeme.vector = self.mem.alloc(vec_len, sizeof(float)) -# for i, val_str in enumerate(pieces): -# lexeme.vector[i] = float(val_str) -# norm = 0.0 -# for i in range(vec_len): -# norm += lexeme.vector[i] * lexeme.vector[i] -# lexeme.l2_norm = sqrt(norm) -# self.vectors_length = vec_len -# return vec_len -# -# def load_vectors_from_bin_loc(self, loc): -# """ -# Load vectors from the location of a binary file. -# -# Arguments: -# loc (unicode): The path of the binary file to load from. -# -# Returns: -# vec_len (int): The length of the vectors loaded. -# """ -# cdef CFile file_ = CFile(loc, b'rb') -# cdef int32_t word_len -# cdef int32_t vec_len = 0 -# cdef int32_t prev_vec_len = 0 -# cdef float* vec -# cdef Address mem -# cdef attr_t string_id -# cdef bytes py_word -# cdef vector[float*] vectors -# cdef int line_num = 0 -# cdef Pool tmp_mem = Pool() -# while True: -# try: -# file_.read_into(&word_len, sizeof(word_len), 1) -# except IOError: -# break -# file_.read_into(&vec_len, sizeof(vec_len), 1) -# if prev_vec_len != 0 and vec_len != prev_vec_len: -# raise VectorReadError.mismatched_sizes(loc, line_num, -# vec_len, prev_vec_len) -# if 0 >= vec_len >= MAX_VEC_SIZE: -# raise VectorReadError.bad_size(loc, vec_len) -# -# chars = file_.alloc_read(tmp_mem, word_len, sizeof(char)) -# vec = file_.alloc_read(self.mem, vec_len, sizeof(float)) -# -# string_id = self.strings[chars[:word_len]] -# # Insert words into vocab to add vector. -# self.get_by_orth(self.mem, string_id) -# while string_id >= vectors.size(): -# vectors.push_back(EMPTY_VEC) -# assert vec != NULL -# vectors[string_id] = vec -# line_num += 1 -# cdef LexemeC* lex -# cdef size_t lex_addr -# cdef double norm = 0.0 -# cdef int i -# for orth, lex_addr in self._by_orth.items(): -# lex = lex_addr -# if lex.lower < vectors.size(): -# lex.vector = vectors[lex.lower] -# norm = 0.0 -# for i in range(vec_len): -# norm += lex.vector[i] * lex.vector[i] -# lex.l2_norm = sqrt(norm) -# else: -# lex.vector = EMPTY_VEC -# self.vectors_length = vec_len -# return vec_len -# -# -#def write_binary_vectors(in_loc, out_loc): -# cdef CFile out_file = CFile(out_loc, 'wb') -# cdef Address mem -# cdef int32_t word_len -# cdef int32_t vec_len -# cdef char* chars -# with bz2.BZ2File(in_loc, 'r') as file_: -# for line in file_: -# pieces = line.split() -# word = pieces.pop(0) -# mem = Address(len(pieces), sizeof(float)) -# vec = mem.ptr -# for i, val_str in enumerate(pieces): -# vec[i] = float(val_str) -# -# word_len = len(word) -# vec_len = len(pieces) -# -# out_file.write_from(&word_len, 1, sizeof(word_len)) -# out_file.write_from(&vec_len, 1, sizeof(vec_len)) -# -# chars = word -# out_file.write_from(chars, len(word), sizeof(char)) -# out_file.write_from(vec, vec_len, sizeof(float)) -# -# -# def resize_vectors(self, int new_size): -# """ -# Set vectors_length to a new size, and allocate more memory for the Lexeme -# vectors if necessary. The memory will be zeroed. -# -# Arguments: -# new_size (int): The new size of the vectors. -# """ -# cdef hash_t key -# cdef size_t addr -# if new_size > self.vectors_length: -# for key, addr in self._by_hash.items(): -# lex = addr -# lex.vector = self.mem.realloc(lex.vector, -# new_size * sizeof(lex.vector[0])) -# self.vectors_length = new_size -# -# - -# -# def dump(self, loc=None): -# """ -# Save the lexemes binary data to the given location, or -# return a byte-string with the data if loc is None. -# -# Arguments: -# loc (Path or None): The path to save to, or None. -# """ -# if loc is None: -# return self.to_bytes() -# else: -# return self.to_disk(loc) -# -# def load_lexemes(self, loc): -# """ -# Load the binary vocabulary data from the given location. -# -# Arguments: -# loc (Path): The path to load from. -# -# Returns: -# None -# """ -# fp = CFile(loc, 'rb', -# on_open_error=lambda: IOError('LexemeCs file not found at %s' % loc)) -# cdef LexemeC* lexeme = NULL -# cdef SerializedLexemeC lex_data -# cdef hash_t key -# cdef unicode py_str -# cdef attr_t orth = 0 -# assert sizeof(orth) == sizeof(lexeme.orth) -# i = 0 -# while True: -# try: -# fp.read_into(&orth, 1, sizeof(orth)) -# except IOError: -# break -# lexeme = self.mem.alloc(sizeof(LexemeC), 1) -# # Copy data from the file into the lexeme -# fp.read_into(&lex_data.data, 1, sizeof(lex_data.data)) -# Lexeme.c_from_bytes(lexeme, lex_data) -# -# lexeme.vector = EMPTY_VEC -# py_str = self.strings[lexeme.orth] -# key = hash_string(py_str) -# self._by_hash.set(key, lexeme) -# self._by_orth.set(lexeme.orth, lexeme) -# self.length += 1 -# i += 1 -# fp.close() diff --git a/website/_harp.json b/website/_harp.json index bf31be30c..1c27426f4 100644 --- a/website/_harp.json +++ b/website/_harp.json @@ -5,7 +5,7 @@ "SITENAME": "spaCy", "SLOGAN": "Industrial-strength Natural Language Processing in Python", - "SITE_URL": "https://spacy.io", + "SITE_URL": "https://alpha.spacy.io", "EMAIL": "contact@explosion.ai", "COMPANY": "Explosion AI", @@ -14,8 +14,8 @@ "SPACY_VERSION": "1.8", "LATEST_NEWS": { - "url": "https://survey.spacy.io/", - "title": "Take the spaCy user survey and help us improve the library!" + "url": "https://github.com/explosion/spaCy/releases/tag/v2.0.0-alpha", + "title": "Test spaCy v2.0.0 alpha!" }, "SOCIAL": { @@ -71,15 +71,59 @@ { "id": 3, "title": "3.x", "checked": true }] }, { "id": "config", "title": "Configuration", "multiple": true, "options": [ - {"id": "venv", "title": "virtualenv", "help": "Use a virtual environment and install spaCy into a user directory" }] + {"id": "venv", "title": "virtualenv", "help": "Use a virtual environment and install spaCy into a user directory" }, + {"id": "gpu", "title": "GPU", "help": "Run spaCy on GPU to make it faster. Requires an NVDIA graphics card with CUDA 2+. See section below for more info."}] }, { "id": "model", "title": "Models", "multiple": true, "options": [ { "id": "en", "title": "English", "meta": "50MB" }, { "id": "de", "title": "German", "meta": "645MB" }, - { "id": "fr", "title": "French", "meta": "1.33GB" }] + { "id": "fr", "title": "French", "meta": "1.33GB" }, + { "id": "es", "title": "Spanish", "meta": "377MB"}] } ], + "QUICKSTART_MODELS": [ + { "id": "lang", "title": "Language", "options": [ + { "id": "en", "title": "English", "checked": true }, + { "id": "de", "title": "German" }, + { "id": "fr", "title": "French" }, + { "id": "es", "title": "Spanish" }] + }, + { "id": "load", "title": "Loading style", "options": [ + { "id": "spacy", "title": "Use spacy.load()", "checked": true, "help": "Use spaCy's built-in loader to load the model by name." }, + { "id": "module", "title": "Import as module", "help": "Import the model explicitly as a Python module." }] + }, + { "id": "config", "title": "Options", "multiple": true, "options": [ + { "id": "example", "title": "Show usage example" }] + } + ], + + "MODELS": { + "en": [ + { "id": "en_core_web_sm", "lang": "English", "feats": [1, 1, 1, 1], "size": "50 MB", "license": "CC BY-SA", "def": true }, + { "id": "en_core_web_md", "lang": "English", "feats": [1, 1, 1, 1], "size": "1 GB", "license": "CC BY-SA" }, + { "id": "en_depent_web_md", "lang": "English", "feats": [1, 1, 1, 0], "size": "328 MB", "license": "CC BY-SA" }, + { "id": "en_vectors_glove_md", "lang": "English", "feats": [1, 0, 0, 1], "size": "727 MB", "license": "CC BY-SA" } + ], + "de": [ + { "id": "de_core_news_md", "lang": "German", "feats": [1, 1, 1, 1], "size": "645 MB", "license": "CC BY-SA" } + ], + "fr": [ + { "id": "fr_depvec_web_lg", "lang": "French", "feats": [1, 1, 0, 1], "size": "1.33 GB", "license": "CC BY-NC" } + ], + "es": [ + { "id": "es_core_web_md", "lang": "Spanish", "feats": [1, 1, 1, 1], "size": "377 MB", "license": "CC BY-SA"} + ] + }, + + "EXAMPLE_SENTENCES": { + "en": "This is a sentence.", + "de": "Dies ist ein Satz.", + "fr": "C'est une phrase.", + "es": "Esto es una frase." + }, + + "ALPHA": true, "V_CSS": "1.6", "V_JS": "1.2", "DEFAULT_SYNTAX": "python", diff --git a/website/_includes/_functions.jade b/website/_includes/_functions.jade index 754ae1a4f..e88e678cb 100644 --- a/website/_includes/_functions.jade +++ b/website/_includes/_functions.jade @@ -19,5 +19,17 @@ //- Generate GitHub links - function gh(repo, filepath, branch) { +- var branch = ALPHA ? 'develop' : branch - return 'https://github.com/' + SOCIAL.github + '/' + repo + (filepath ? '/blob/' + (branch || 'master') + '/' + filepath : '' ); - } + + +//- Get social images + +- function getSocialImg() { +- var base = SITE_URL + '/assets/img/social/preview_' +- var image = ALPHA ? 'alpha' : 'default' +- if (preview) image = preview +- else if (SECTION == 'docs' && !ALPHA) image = 'docs' +- return base + image + '.jpg' +- } diff --git a/website/_includes/_mixins-base.jade b/website/_includes/_mixins-base.jade index e75ef36c8..7534a6f4e 100644 --- a/website/_includes/_mixins-base.jade +++ b/website/_includes/_mixins-base.jade @@ -37,15 +37,17 @@ mixin svg(file, name, width, height) size - [integer] icon width and height (default: 20) mixin icon(name, size) - +svg("icons", name, size || 20).o-icon&attributes(attributes) + - var size = size || 20 + +svg("icons", name, size).o-icon(style="min-width: #{size}px")&attributes(attributes) //- Pro/Con/Neutral icon icon - [string] "pro", "con" or "neutral" (default: "neutral") + size - [integer] icon size (optional) -mixin procon(icon) +mixin procon(icon, size) - colors = { pro: "green", con: "red", neutral: "yellow" } - +icon(icon)(class="u-color-#{colors[icon] || 'subtle'}" aria-label=icon)&attributes(attributes) + +icon(icon, size)(class="u-color-#{colors[icon] || 'subtle'}" aria-label=icon)&attributes(attributes) //- Headlines Helper Mixin @@ -91,36 +93,47 @@ mixin permalink(id) groups - [object] option groups, uses global variable QUICKSTART headline - [string] optional text to be rendered as widget headline -mixin quickstart(groups, headline) - .c-quickstart.o-block#qs +mixin quickstart(groups, headline, description, hide_results) + .c-quickstart.o-block-small#qs .c-quickstart__content if headline +h(2)=headline + if description + p=description for group in groups .c-quickstart__group.u-text-small(data-qs-group=group.id) - .c-quickstart__legend=group.title + if group.title + .c-quickstart__legend=group.title + if group.help + | #[+help(group.help)] .c-quickstart__fields for option in group.options - input.c-quickstart__input(class="c-quickstart__input--" + (group.multiple ? "check" : "radio") type=group.multiple ? "checkbox" : "radio" name=group.id id=option.id value=option.id checked=option.checked) - label.c-quickstart__label(for=option.id)=option.title + input.c-quickstart__input(class="c-quickstart__input--" + (group.input_style ? group.input_style : group.multiple ? "check" : "radio") type=group.multiple ? "checkbox" : "radio" name=group.id id="qs-#{option.id}" value=option.id checked=option.checked) + label.c-quickstart__label(for="qs-#{option.id}")!=option.title if option.meta | #[span.c-quickstart__label__meta (#{option.meta})] if option.help - | #[+help(option.help).c-quickstart__label__meta] + | #[+help(option.help)] - pre.c-code-block - code.c-code-block__content.c-quickstart__code(data-qs-results="") - block + if hide_results + block + else + pre.c-code-block + code.c-code-block__content.c-quickstart__code(data-qs-results="") + block + + .c-quickstart__info.u-text-tiny.o-block.u-text-right + | Like this widget? Check out #[+a("https://github.com/ines/quickstart").u-link quickstart.js]! //- Quickstart code item data [object] - Rendering conditions (keyed by option group ID, value: option) -mixin qs(data) +mixin qs(data, style) - args = {} for value, setting in data - args['data-qs-' + setting] = value - span.c-quickstart__line&attributes(args) + span.c-quickstart__line(class="c-quickstart__line--#{style || 'bash'}")&attributes(args) block @@ -177,3 +190,14 @@ mixin landing-header() mixin landing-badge(url, graphic, alt, size) +a(url)(aria-label=alt title=alt).c-landing__badge +svg("graphics", graphic, size || 225) + + +//- Under construction (temporary) + Marks sections that still need to be completed for the v2.0 release. + +mixin under-construction() + +infobox("🚧 Under construction") + | This section is still being written and will be updated for the v2.0 + | release. Is there anything that you think should definitely mentioned or + | explained here? Any examples you'd like to see? #[strong Let us know] + | on the #[+a(gh("spacy") + "/issues/1105") v2.0 alpha thread] on GitHub! diff --git a/website/_includes/_mixins.jade b/website/_includes/_mixins.jade index a72696658..16514bcda 100644 --- a/website/_includes/_mixins.jade +++ b/website/_includes/_mixins.jade @@ -34,17 +34,17 @@ mixin src(url) +a(url) block - | #[+icon("code", 16).o-icon--inline.u-color-subtle] + | #[+icon("code", 16).o-icon--inline.u-color-theme] //- API link (with added tag and automatically generated path) path - [string] path to API docs page relative to /docs/api/ mixin api(path) - +a("/docs/api/" + path, true)(target="_self").u-no-border.u-inline-block + +a("/docs/api/" + path, true)(target="_self").u-no-border.u-inline-block.u-nowrap block - | #[+icon("book", 18).o-icon--inline.u-color-subtle] + | #[+icon("book", 18).o-icon--inline.u-color-theme] //- Help icon with tooltip @@ -103,16 +103,34 @@ mixin button(url, trusted, ...style) label - [string] aside title (optional or false for no label) language - [string] language for syntax highlighting (default: "python") supports basic relevant languages available for PrismJS + icon - [string] icon to display next to code block, mostly used for old/new + height - [integer] optional height to clip code block to -mixin code(label, language) - pre.c-code-block.o-block(class="lang-#{(language || DEFAULT_SYNTAX)}")&attributes(attributes) +mixin code(label, language, icon, height) + pre.c-code-block.o-block(class="lang-#{(language || DEFAULT_SYNTAX)}" class=icon ? "c-code-block--has-icon" : null style=height ? "height: #{height}px" : null)&attributes(attributes) if label h4.u-text-label.u-text-label--dark=label + if icon + - var classes = {'accept': 'u-color-green', 'reject': 'u-color-red'} + .c-code-block__icon(class=classes[icon] || null class=classes[icon] ? "c-code-block__icon--border" : null) + +icon(icon, 18) + code.c-code-block__content block +//- Code blocks to display old/new versions + +mixin code-old() + +code(false, false, "reject").o-block-small + block + +mixin code-new() + +code(false, false, "accept").o-block-small + block + + //- CodePen embed slug - [string] ID of CodePen demo (taken from URL) height - [integer] height of demo embed iframe @@ -160,10 +178,31 @@ mixin label() //- Tag mixin tag() - span.u-text-tag.u-text-tag--spaced(aria-hidden="true") + span.u-text-tag.u-text-tag--spaced(aria-hidden="true")&attributes(attributes) block +//- "Requires model" tag with tooltip and list of capabilities + ...capabs - [string] Required model capabilities, e.g. "vectors". + +mixin tag-model(...capabs) + - var intro = "To use this functionality, spaCy needs a model to be installed" + - var ext = capabs.length ? " that supports the following capabilities: " + capabs.join(', ') : "" + +tag Requires model + +help(intro + ext + ".").u-color-theme + + +//- "New" tag to label features new in a specific version + By using a separate mixin with a version ID, it becomes easy to quickly + enable/disable tags without having to modify the markup in the docs. + version - [string or integer] version number, without "v" prefix + +mixin tag-new(version) + - var version = (typeof version == 'number') ? version.toFixed(1) : version + +tag(data-tooltip="This feature is new and was introduced in spaCy v#{version}.") + | v#{version} + + //- List type - [string] "numbers", "letters", "roman" (bulleted list if none set) start - [integer] start number @@ -324,7 +363,34 @@ mixin pos-row(tag, pos, morph, desc) | #[code=m] +cell.u-text-small=desc + mixin dep-row(label, desc) +row +cell #[code=label] +cell=desc + + +//- Table rows for linguistic annotations + annots [array] - array of cell content + style [array] array of 1 (display as code) or 0 (display as text) + +mixin annotation-row(annots, style) + +row + for cell, i in annots + if style && style[i] + - cell = (typeof(cell) != 'boolean') ? cell : cell ? 'True' : 'False' + +cell #[code=cell] + else + +cell=cell + block + + +//- Table of contents, to be used with +item mixins for links + col - [string] width of column (see +grid-col) + +mixin table-of-contents(col) + +grid-col(col || "half") + +infobox + +label.o-block-small Table of contents + +list("numbers").u-text-small.o-no-block + block diff --git a/website/_includes/_navigation.jade b/website/_includes/_navigation.jade index d319ef2c9..f113ca3f4 100644 --- a/website/_includes/_navigation.jade +++ b/website/_includes/_navigation.jade @@ -9,7 +9,9 @@ nav.c-nav.u-text.js-nav(class=landing ? "c-nav--theme" : null) .u-text-label.u-padding-small.u-hidden-xs=SUBSECTION ul.c-nav__menu - each url, item in NAVIGATION + - var NAV = ALPHA ? { "Usage": "/docs/usage", "Reference": "/docs/api" } : NAVIGATION + + each url, item in NAV li.c-nav__menu__item(class=(url == "/") ? "u-hidden-xs" : null) +a(url)=item diff --git a/website/_includes/_page-docs.jade b/website/_includes/_page-docs.jade index 72db134cd..7afbc6bdc 100644 --- a/website/_includes/_page-docs.jade +++ b/website/_includes/_page-docs.jade @@ -6,9 +6,28 @@ include _sidebar main.o-main.o-main--sidebar.o-main--aside article.o-content - +h(1)=title - if tag - +tag=tag + +grid.o-no-block + +grid-col(source ? "two-thirds" : "full") + +h(1)=title + if tag + +tag=tag + + if source + +grid-col("third").u-text-right + .o-inline-list + +button(gh("spacy", source), false, "secondary").u-text-tag Source #[+icon("code", 14)] + + + if ALPHA + +infobox("⚠️ You are viewing the spaCy v2.0.0 alpha docs") + strong This page is part of the alpha documentation for spaCy v2.0. + | It does not reflect the state of the latest stable release. + | Because v2.0 is still under development, the implementation + | may differ from the intended state described here. See the + | #[+a(gh("spaCy") + "/releases/tag/v2.0.0-alpha") release notes] + | for details on how to install and test the new version. To + | read the official docs for spaCy v1.x, + | #[+a("https://spacy.io/docs") go here]. !=yield diff --git a/website/_layout.jade b/website/_layout.jade index ccca2863f..482af35fa 100644 --- a/website/_layout.jade +++ b/website/_layout.jade @@ -24,18 +24,21 @@ html(lang="en") meta(property="og:url" content="#{SITE_URL}/#{current.path.join('/')}") meta(property="og:title" content="#{title} - spaCy") meta(property="og:description" content=description) - meta(property="og:image" content="#{SITE_URL}/assets/img/social#{(SECTION == 'docs') ? '_docs' : ''}.jpg") + meta(property="og:image" content=getSocialImg()) meta(name="twitter:card" content="summary_large_image") meta(name="twitter:site" content="@" + SOCIAL.twitter) meta(name="twitter:title" content="#{title} - spaCy") meta(name="twitter:description" content=description) - meta(name="twitter:image" content="#{SITE_URL}/assets/img/social#{(SECTION == 'docs') ? '_docs' : ''}.jpg") + meta(name="twitter:image" content=getSocialImg()) link(rel="shortcut icon" href="/assets/img/favicon.ico") link(rel="icon" type="image/x-icon" href="/assets/img/favicon.ico") - if SUBSECTION == "usage" + if ALPHA && SECTION == "docs" + link(href="/assets/css/style_green.css?v#{V_CSS}" rel="stylesheet") + + else if SUBSECTION == "usage" link(href="/assets/css/style_red.css?v#{V_CSS}" rel="stylesheet") else diff --git a/website/assets/css/_components/_code.sass b/website/assets/css/_components/_code.sass index 83462ef72..2e1856c0a 100644 --- a/website/assets/css/_components/_code.sass +++ b/website/assets/css/_components/_code.sass @@ -13,6 +13,20 @@ white-space: pre direction: ltr + &.c-code-block--has-icon + padding: 0 + display: flex + +.c-code-block__icon + padding: 0 0 0 1rem + display: flex + justify-content: center + align-items: center + + &.c-code-block__icon--border + border-left: 6px solid + + //- Code block content @@ -26,8 +40,8 @@ *:not(.c-code-block) > code font: normal 600 0.8em/#{1} $font-code - background: rgba($color-front, 0.05) - box-shadow: 1px 1px 0 rgba($color-front, 0.1) + background: darken($color-theme-light, 5) + box-shadow: 1px 1px 0 rgba($color-front, 0.05) text-shadow: 1px 1px 0 rgba($color-back, 0.5) color: $color-front padding: 0.1em 0.5em diff --git a/website/assets/css/_components/_quickstart.sass b/website/assets/css/_components/_quickstart.sass index 4065940bc..1e7d0761a 100644 --- a/website/assets/css/_components/_quickstart.sass +++ b/website/assets/css/_components/_quickstart.sass @@ -6,6 +6,9 @@ display: none background: $color-subtle-light + &:not([style]) + .c-quickstart__info + display: none + .c-quickstart__content padding: 2rem 3rem @@ -81,7 +84,15 @@ &:before color: $color-theme margin-right: 1em + + &.c-quickstart__line--bash:before content: "$" + &.c-quickstart__line--python:before + content: ">>>" + + &.c-quickstart__line--divider + padding: 1.5rem 0 + .c-quickstart__code font-size: 1.6rem diff --git a/website/assets/css/_components/_sidebar.sass b/website/assets/css/_components/_sidebar.sass index 50319929d..d88588341 100644 --- a/website/assets/css/_components/_sidebar.sass +++ b/website/assets/css/_components/_sidebar.sass @@ -10,7 +10,7 @@ @include position(fixed, top, left, 0, 0) @include size($sidebar-width, 100vh) flex: 0 0 $sidebar-width - padding: calc(#{$nav-height} + 1.5rem) 0 2rem + padding: calc(#{$nav-height} + 1.5rem) 0 0 z-index: 10 border-right: 1px solid $color-subtle diff --git a/website/assets/css/_components/_tooltips.sass b/website/assets/css/_components/_tooltips.sass index e5de5f9a3..e68f2875c 100644 --- a/website/assets/css/_components/_tooltips.sass +++ b/website/assets/css/_components/_tooltips.sass @@ -10,16 +10,15 @@ content: attr(data-tooltip) background: $color-front border-radius: 2px + border: 1px solid rgba($color-subtle-dark, 0.5) color: $color-back - font-family: inherit - font-size: 1.3rem - line-height: 1.25 + font: normal 1.3rem/#{1.25} $font-primary + text-transform: none opacity: 0 padding: 0.5em 0.75em transform: translateX(-50%) translateY(-2px) transition: opacity 0.1s ease-out, transform 0.1s ease-out visibility: hidden - //white-space: nowrap min-width: 200px max-width: 300px z-index: 200 diff --git a/website/assets/css/_variables.sass b/website/assets/css/_variables.sass index d6ab548b4..3ccf36f06 100644 --- a/website/assets/css/_variables.sass +++ b/website/assets/css/_variables.sass @@ -26,7 +26,7 @@ $font-code: 'Source Code Pro', Consolas, 'Andale Mono', Menlo, Monaco, Courier, // Colors -$colors: ( blue: #09a3d5, red: #d9515d ) +$colors: ( blue: #09a3d5, red: #d9515d, green: #08c35e ) $color-back: #fff !default $color-front: #1a1e23 !default diff --git a/website/assets/css/style_green.sass b/website/assets/css/style_green.sass new file mode 100644 index 000000000..c7369f990 --- /dev/null +++ b/website/assets/css/style_green.sass @@ -0,0 +1,4 @@ +//- 💫 STYLESHEET (GREEN) + +$theme: green +@import style diff --git a/website/assets/img/docs/architecture.svg b/website/assets/img/docs/architecture.svg index d62d08f88..c1d12d79b 100644 --- a/website/assets/img/docs/architecture.svg +++ b/website/assets/img/docs/architecture.svg @@ -1,128 +1,128 @@ - + - - Language - - + + Language + + - MAKES - - + MAKES + + - nlp.vocab.morphology - - Vocab - - + nlp.vocab.morphology + + Vocab + + - nlp.vocab - - StringStore - - + nlp.vocab + + StringStore + + - nlp.vocab.strings - - + nlp.vocab.strings + + - nlp.tokenizer.vocab - - Tokenizer - - + nlp.tokenizer.vocab + + Tokenizer + + - nlp.make_doc() - - + nlp.make_doc() + + - nlp.pipeline - - + nlp.pipeline + + - nlp.pipeline[i].vocab - - pt - - en - - de - - fr - - es - - it - - nl - - sv - - fi - - nb - - hu - - he - - bn - - ja - - zh - - - - + nlp.pipeline[i].vocab + + pt + + en + + de + + fr + + es + + it + + nl + + sv + + fi + + nb + + hu + + he + + bn + + ja + + zh + + + + - doc.vocab - - + doc.vocab + + - MAKES - - Doc - - + MAKES + + Doc + + - MAKES - - + MAKES + + - token.doc - - Token - - Span - - + token.doc + + Token + + Span + + - lexeme.vocab - - Lexeme - - + lexeme.vocab + + Lexeme + + - MAKES - - + MAKES + + - span.doc - - Dependency Parser - - Entity Recognizer - - Tagger - - Matcher - - Lemmatizer - - Morphology + span.doc + + Dependency Parser + + Entity Recognizer + + Tagger + + Matcher + + Lemmatizer + + Morphology diff --git a/website/assets/img/docs/language_data.svg b/website/assets/img/docs/language_data.svg index 4662d4c01..31e1a1b29 100644 --- a/website/assets/img/docs/language_data.svg +++ b/website/assets/img/docs/language_data.svg @@ -1,13 +1,13 @@ - Tokenizer + Tokenizer @@ -17,7 +17,7 @@ - Base data + Base data @@ -33,50 +33,50 @@ - Language data + Language data - stop words + stop words - lexical attributes + lexical attributes - tokenizer exceptions + tokenizer exceptions - prefixes, suffixes, infixes + prefixes, suffixes, infixes - lemma data + lemma data - Lemmatizer + Lemmatizer - char classes + char classes - Token + Token - morph rules + morph rules - tag map + tag map - Morphology + Morphology diff --git a/website/assets/img/docs/pipeline.svg b/website/assets/img/docs/pipeline.svg new file mode 100644 index 000000000..9c34636dc --- /dev/null +++ b/website/assets/img/docs/pipeline.svg @@ -0,0 +1,30 @@ + + + + + Doc + + + + Text + + + + nlp + + tokenizer + + tensorizer + + + + tagger + + parser + + ner + diff --git a/website/assets/img/docs/tokenization.svg b/website/assets/img/docs/tokenization.svg new file mode 100644 index 000000000..f5b164725 --- /dev/null +++ b/website/assets/img/docs/tokenization.svg @@ -0,0 +1,123 @@ + + + + + “Let’s + + + go + + + to + + + N.Y.!” + + + + + + Let’s + + + go + + + to + + + N.Y.!” + + + + + Let + + + go + + + to + + + N.Y.!” + + + ’s + + + + + + Let + + + go + + + to + + + N.Y.! + + + ’s + + + + + + + + + Let + + + go + + + to + + + N.Y. + + + ’s + + + + + + ! + + + + Let + + go + + to + + N.Y. + + ’s + + + + ! + + EXCEPTION + + PREFIX + + SUFFIX + + SUFFIX + + EXCEPTION + + DONE + diff --git a/website/assets/img/docs/training-loop.svg b/website/assets/img/docs/training-loop.svg new file mode 100644 index 000000000..e670f816a --- /dev/null +++ b/website/assets/img/docs/training-loop.svg @@ -0,0 +1,40 @@ + + + + + + + + Training data + + + + label + + + + text + + + + + + Doc + + + + GoldParse + + + + update + + nlp + + + + optimizer + diff --git a/website/assets/img/docs/training.svg b/website/assets/img/docs/training.svg new file mode 100644 index 000000000..cd6b74f04 --- /dev/null +++ b/website/assets/img/docs/training.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + PREDICT + + + + SAVE + + Model + + + + + + Training data + + + + label + + + + label + + Updated + Model + + + text + + + + GRADIENT + diff --git a/website/assets/img/docs/vocab_stringstore.svg b/website/assets/img/docs/vocab_stringstore.svg new file mode 100644 index 000000000..119175247 --- /dev/null +++ b/website/assets/img/docs/vocab_stringstore.svg @@ -0,0 +1,77 @@ + + + + + 31979... + + Lexeme + + 46904... + + Lexeme + + 37020... + + Lexeme + + + "coffee" + + 31979… + + "I" + + 46904… + + "love" + + 37020… + + + + + nsubj + + + + dobj + + String + Store + + Vocab + + Doc + + love + VERB + + Token + + I + PRON + + Token + + coffee + NOUN + + Token + + + + + + + + + + + + + diff --git a/website/assets/img/graphics.svg b/website/assets/img/graphics.svg index c24473b4c..a449c3d04 100644 --- a/website/assets/img/graphics.svg +++ b/website/assets/img/graphics.svg @@ -1,5 +1,16 @@ + + spaCy v2.0.0 alpha + + + + + + + + + spaCy user survey 2017 diff --git a/website/assets/img/icons.svg b/website/assets/img/icons.svg index e970bb52c..104117cc0 100644 --- a/website/assets/img/icons.svg +++ b/website/assets/img/icons.svg @@ -30,5 +30,14 @@ + + + + + + + + + diff --git a/website/assets/img/pattern_green.jpg b/website/assets/img/pattern_green.jpg new file mode 100644 index 000000000..d2e341822 Binary files /dev/null and b/website/assets/img/pattern_green.jpg differ diff --git a/website/assets/img/social/preview_101.jpg b/website/assets/img/social/preview_101.jpg new file mode 100644 index 000000000..448c63eaf Binary files /dev/null and b/website/assets/img/social/preview_101.jpg differ diff --git a/website/assets/img/social/preview_alpha.jpg b/website/assets/img/social/preview_alpha.jpg new file mode 100644 index 000000000..6da622569 Binary files /dev/null and b/website/assets/img/social/preview_alpha.jpg differ diff --git a/website/assets/img/social.jpg b/website/assets/img/social/preview_default.jpg similarity index 100% rename from website/assets/img/social.jpg rename to website/assets/img/social/preview_default.jpg diff --git a/website/assets/img/social_docs.jpg b/website/assets/img/social/preview_docs.jpg similarity index 100% rename from website/assets/img/social_docs.jpg rename to website/assets/img/social/preview_docs.jpg diff --git a/website/docs/api/_annotation/_dep-labels.jade b/website/docs/api/_annotation/_dep-labels.jade index 9e1e89324..427b2f53a 100644 --- a/website/docs/api/_annotation/_dep-labels.jade +++ b/website/docs/api/_annotation/_dep-labels.jade @@ -1,10 +1,5 @@ //- 💫 DOCS > API > ANNOTATION > DEPENDENCY LABELS -+infobox("Tip") - | In spaCy v1.8.3+, you can also use #[code spacy.explain()] to get the - | description for the string representation of a label. For example, - | #[code spacy.explain("prt")] will return "particle". - +h(3, "dependency-parsing-english") English dependency labels p diff --git a/website/docs/api/_annotation/_named-entities.jade b/website/docs/api/_annotation/_named-entities.jade index 68b3bd17d..476659d4a 100644 --- a/website/docs/api/_annotation/_named-entities.jade +++ b/website/docs/api/_annotation/_named-entities.jade @@ -1,10 +1,5 @@ //- 💫 DOCS > API > ANNOTATION > NAMED ENTITIES -+infobox("Tip") - | In spaCy v1.8.3+, you can also use #[code spacy.explain()] to get the - | description for the string representation of an entity label. For example, - | #[code spacy.explain("LANGUAGE")] will return "any named language". - +table([ "Type", "Description" ]) +row +cell #[code PERSON] diff --git a/website/docs/api/_annotation/_pos-tags.jade b/website/docs/api/_annotation/_pos-tags.jade index d3ceef777..ea3a225bf 100644 --- a/website/docs/api/_annotation/_pos-tags.jade +++ b/website/docs/api/_annotation/_pos-tags.jade @@ -1,10 +1,5 @@ //- 💫 DOCS > API > ANNOTATION > POS TAGS -+infobox("Tip") - | In spaCy v1.8.3+, you can also use #[code spacy.explain()] to get the - | description for the string representation of a tag. For example, - | #[code spacy.explain("RB")] will return "adverb". - +h(3, "pos-tagging-english") English part-of-speech tag scheme p diff --git a/website/docs/api/_data.json b/website/docs/api/_data.json index af48a9ceb..e413f200c 100644 --- a/website/docs/api/_data.json +++ b/website/docs/api/_data.json @@ -3,7 +3,13 @@ "Introduction": { "Facts & Figures": "./", "Languages": "language-models", - "Philosophy": "philosophy" + "Annotation Specs": "annotation" + }, + "Top-level": { + "spacy": "spacy", + "displacy": "displacy", + "Utility Functions": "util", + "Command line": "cli" }, "Classes": { "Doc": "doc", @@ -11,21 +17,19 @@ "Span": "span", "Language": "language", "Tokenizer": "tokenizer", + "Tensorizer": "tensorizer", "Tagger": "tagger", "DependencyParser": "dependencyparser", "EntityRecognizer": "entityrecognizer", + "TextCategorizer": "textcategorizer", "Matcher": "matcher", "Lexeme": "lexeme", "Vocab": "vocab", "StringStore": "stringstore", - "GoldParse": "goldparse" - }, - "Other": { - "Command line": "cli", - "displaCy": "displacy", - "Utility Functions": "util", - "Annotation Specs": "annotation", - "Feature Scheme": "features" + "Vectors": "vectors", + "GoldParse": "goldparse", + "GoldCorpus": "goldcorpus", + "Binder": "binder" } }, @@ -43,95 +47,145 @@ "title": "Philosophy" }, - "language": { - "title": "Language", - "tag": "class" - }, - - "doc": { - "title": "Doc", - "tag": "class" - }, - - "token": { - "title": "Token", - "tag": "class" - }, - - "span": { - "title": "Span", - "tag": "class" - }, - - "lexeme": { - "title": "Lexeme", - "tag": "class" - }, - - "vocab": { - "title": "Vocab", - "tag": "class" - }, - - "stringstore": { - "title": "StringStore", - "tag": "class" - }, - - "matcher": { - "title": "Matcher", - "tag": "class" - }, - - "dependenyparser": { - "title": "DependencyParser", - "tag": "class" - }, - - "entityrecognizer": { - "title": "EntityRecognizer", - "tag": "class" - }, - - "dependencyparser": { - "title": "DependencyParser", - "tag": "class" - }, - - "tokenizer": { - "title": "Tokenizer", - "tag": "class" - }, - - "tagger": { - "title": "Tagger", - "tag": "class" - }, - - "goldparse": { - "title": "GoldParse", - "tag": "class" - }, - - "cli": { - "title": "Command Line Interface", + "spacy": { + "title": "spaCy top-level functions", + "source": "spacy/__init__.py", "next": "displacy" }, "displacy": { "title": "displaCy", - "tag": "module" + "tag": "module", + "source": "spacy/displacy", + "next": "util" }, "util": { - "title": "Utility Functions" + "title": "Utility Functions", + "source": "spacy/util.py", + "next": "cli" + }, + + "cli": { + "title": "Command Line Interface", + "source": "spacy/cli" + }, + + "language": { + "title": "Language", + "tag": "class", + "source": "spacy/language.py" + }, + + "doc": { + "title": "Doc", + "tag": "class", + "source": "spacy/tokens/doc.pyx" + }, + + "token": { + "title": "Token", + "tag": "class", + "source": "spacy/tokens/token.pyx" + }, + + "span": { + "title": "Span", + "tag": "class", + "source": "spacy/tokens/span.pyx" + }, + + "lexeme": { + "title": "Lexeme", + "tag": "class", + "source": "spacy/lexeme.pyx" + }, + + "vocab": { + "title": "Vocab", + "tag": "class", + "source": "spacy/vocab.pyx" + }, + + "stringstore": { + "title": "StringStore", + "tag": "class", + "source": "spacy/strings.pyx" + }, + + "matcher": { + "title": "Matcher", + "tag": "class", + "source": "spacy/matcher.pyx" + }, + + "dependenyparser": { + "title": "DependencyParser", + "tag": "class", + "source": "spacy/pipeline.pyx" + }, + + "entityrecognizer": { + "title": "EntityRecognizer", + "tag": "class", + "source": "spacy/pipeline.pyx" + }, + + "textcategorizer": { + "title": "TextCategorizer", + "tag": "class", + "source": "spacy/pipeline.pyx" + }, + + "dependencyparser": { + "title": "DependencyParser", + "tag": "class", + "source": "spacy/pipeline.pyx" + }, + + "tokenizer": { + "title": "Tokenizer", + "tag": "class", + "source": "spacy/tokenizer.pyx" + }, + + "tagger": { + "title": "Tagger", + "tag": "class", + "source": "spacy/pipeline.pyx" + }, + + "tensorizer": { + "title": "Tensorizer", + "tag": "class", + "source": "spacy/pipeline.pyx" + }, + + "goldparse": { + "title": "GoldParse", + "tag": "class", + "source": "spacy/gold.pyx" + }, + + "goldcorpus": { + "title": "GoldCorpus", + "tag": "class", + "source": "spacy/gold.pyx" + }, + + "binder": { + "title": "Binder", + "tag": "class", + "source": "spacy/tokens/binder.pyx" + }, + + "vectors": { + "title": "Vectors", + "tag": "class", + "source": "spacy/vectors.pyx" }, "annotation": { "title": "Annotation Specifications" - }, - - "features": { - "title": "Linear Model Feature Scheme" } } diff --git a/website/docs/api/annotation.jade b/website/docs/api/annotation.jade index 8c6b8fb10..ce18878b7 100644 --- a/website/docs/api/annotation.jade +++ b/website/docs/api/annotation.jade @@ -14,11 +14,12 @@ p | (#[code ' ']) is included as a token. +aside-code("Example"). - from spacy.en import English - nlp = English(parser=False) + from spacy.lang.en import English + nlp = English() tokens = nlp('Some\nspaces and\ttab characters') - print([t.orth_ for t in tokens]) - # ['Some', '\n', 'spaces', ' ', 'and', '\t', 'tab', 'characters'] + tokens_text = [t.text for t in tokens] + assert tokens_text == ['Some', '\n', 'spaces', ' ', 'and', + '\t', 'tab', 'characters'] p | The whitespace tokens are useful for much the same reason punctuation is @@ -38,6 +39,11 @@ p +h(2, "pos-tagging") Part-of-speech Tagging ++aside("Tip: Understanding tags") + | You can also use #[code spacy.explain()] to get the description for the + | string representation of a tag. For example, + | #[code spacy.explain("RB")] will return "adverb". + include _annotation/_pos-tags +h(2, "lemmatization") Lemmatization @@ -50,27 +56,75 @@ p A "lemma" is the uninflected form of a word. In English, this means: +item #[strong Nouns]: The form like "dog", not "dogs"; like "child", not "children" +item #[strong Verbs]: The form like "write", not "writes", "writing", "wrote" or "written" -+aside("About spaCy's custom pronoun lemma") - | Unlike verbs and common nouns, there's no clear base form of a personal - | pronoun. Should the lemma of "me" be "I", or should we normalize person - | as well, giving "it" — or maybe "he"? spaCy's solution is to introduce a - | novel symbol, #[code.u-nowrap -PRON-], which is used as the lemma for - | all personal pronouns. - p | The lemmatization data is taken from | #[+a("https://wordnet.princeton.edu") WordNet]. However, we also add a | special case for pronouns: all pronouns are lemmatized to the special | token #[code -PRON-]. ++infobox("About spaCy's custom pronoun lemma") + | Unlike verbs and common nouns, there's no clear base form of a personal + | pronoun. Should the lemma of "me" be "I", or should we normalize person + | as well, giving "it" — or maybe "he"? spaCy's solution is to introduce a + | novel symbol, #[code -PRON-], which is used as the lemma for + | all personal pronouns. + +h(2, "dependency-parsing") Syntactic Dependency Parsing ++aside("Tip: Understanding labels") + | You can also use #[code spacy.explain()] to get the description for the + | string representation of a label. For example, + | #[code spacy.explain("prt")] will return "particle". + include _annotation/_dep-labels +h(2, "named-entities") Named Entity Recognition ++aside("Tip: Understanding entity types") + | You can also use #[code spacy.explain()] to get the description for the + | string representation of an entity label. For example, + | #[code spacy.explain("LANGUAGE")] will return "any named language". + include _annotation/_named-entities ++h(3, "biluo") BILUO Scheme + +p + | spaCy translates character offsets into the BILUO scheme, in order to + | decide the cost of each action given the current state of the entity + | recognizer. The costs are then used to calculate the gradient of the + | loss, to train the model. + ++aside("Why BILUO, not IOB?") + | There are several coding schemes for encoding entity annotations as + | token tags. These coding schemes are equally expressive, but not + | necessarily equally learnable. + | #[+a("http://www.aclweb.org/anthology/W09-1119") Ratinov and Roth] + | showed that the minimal #[strong Begin], #[strong In], #[strong Out] + | scheme was more difficult to learn than the #[strong BILUO] scheme that + | we use, which explicitly marks boundary tokens. + ++table([ "Tag", "Description" ]) + +row + +cell #[code #[span.u-color-theme B] EGIN] + +cell The first token of a multi-token entity. + + +row + +cell #[code #[span.u-color-theme I] N] + +cell An inner token of a multi-token entity. + + +row + +cell #[code #[span.u-color-theme L] AST] + +cell The final token of a multi-token entity. + + +row + +cell #[code #[span.u-color-theme U] NIT] + +cell A single-token entity. + + +row + +cell #[code #[span.u-color-theme O] UT] + +cell A non-entity token. + +h(2, "json-input") JSON input format for training p diff --git a/website/docs/api/binder.jade b/website/docs/api/binder.jade new file mode 100644 index 000000000..0dea1b339 --- /dev/null +++ b/website/docs/api/binder.jade @@ -0,0 +1,7 @@ +//- 💫 DOCS > API > BINDER + +include ../../_includes/_mixins + +p A container class for serializing collections of #[code Doc] objects. + ++under-construction diff --git a/website/docs/api/cli.jade b/website/docs/api/cli.jade index e4d762615..e109e4b66 100644 --- a/website/docs/api/cli.jade +++ b/website/docs/api/cli.jade @@ -5,16 +5,23 @@ include ../../_includes/_mixins p | As of v1.7.0, spaCy comes with new command line helpers to download and | link models and show useful debugging information. For a list of available - | commands, type #[code python -m spacy --help]. + | commands, type #[code python -m spacy]. To make the command even more + | convenient, we recommend + | #[+a("https://askubuntu.com/questions/17536/how-do-i-create-a-permanent-bash-alias/17537#17537") creating an alias] + | mapping #[code python -m spacy] to #[code spacy]. +aside("Why python -m?") | The problem with a global entry point is that it's resolved by looking up | entries in your #[code PATH] environment variable. This can give you - | unexpected results, especially when using #[code virtualenv]. For - | instance, you may have spaCy installed on your system but not in your - | current environment. The command will then execute the wrong - | spaCy installation. #[code python -m] prevents fallbacks to system modules - | and makes sure the correct version of spaCy is used. + | unexpected results, like executing the wrong spaCy installation. + | #[code python -m] prevents fallbacks to system modules. + ++infobox("⚠️ Deprecation note") + | As of spaCy 2.0, the #[code model] command to initialise a model data + | directory is deprecated. The command was only necessary because previous + | versions of spaCy expected a model directory to already be set up. This + | has since been changed, so you can use the #[+api("cli#train") #[code train]] + | command straight away. +h(2, "download") Download @@ -45,13 +52,33 @@ p +cell flag +cell Show help message and available arguments. ++aside("Downloading best practices") + | The #[code download] command is mostly intended as a convenient, + | interactive wrapper – it performs compatibility checks and prints + | detailed messages in case things go wrong. It's #[strong not recommended] + | to use this command as part of an automated process. If you know which + | model your project needs, you should consider a + | #[+a("/docs/usage/models#download-pip") direct download via pip], or + | uploading the model to a local PyPi installation and fetching it straight + | from there. This will also allow you to add it as a versioned package + | dependency to your project. +h(2, "link") Link p | Create a #[+a("/docs/usage/models#usage") shortcut link] for a model, | either a Python package or a local directory. This will let you load - | models from any location via #[code spacy.load()]. + | models from any location using a custom name via + | #[+api("spacy#load") #[code spacy.load()]]. + ++infobox("Important note") + | In spaCy v1.x, you had to use the model data directory to set up a shortcut + | link for a local path. As of v2.0, spaCy expects all shortcut links to + | be #[strong loadable model packages]. If you want to load a data directory, + | call #[+api("spacy#load") #[code spacy.load()]] or + | #[+api("language#from_disk") #[code Language.from_disk()]] with the path, + | or use the #[+api("cli#package") #[code package]] command to create a + | model package. +code(false, "bash"). python -m spacy link [origin] [link_name] [--force] @@ -92,7 +119,7 @@ p +row +cell #[code model] +cell positional - +cell Shortcut link of model (optional). + +cell A model, i.e. shortcut link, package name or path (optional). +row +cell #[code --markdown], #[code -md] @@ -105,7 +132,6 @@ p +cell Show help message and available arguments. +h(2, "convert") Convert - +tag experimental p | Convert files into spaCy's #[+a("/docs/api/annotation#json-input") JSON format] @@ -114,7 +140,7 @@ p | the input file. Currently only supports #[code .conllu]. +code(false, "bash"). - python -m spacy convert [input_file] [output_dir] [--n_sents] [--morphology] + python -m spacy convert [input_file] [output_dir] [--n-sents] [--morphology] +table(["Argument", "Type", "Description"]) +row @@ -128,7 +154,7 @@ p +cell Output directory for converted JSON file. +row - +cell #[code --n_sents], #[code -n] + +cell #[code --n-sents], #[code -n] +cell option +cell Number of sentences per document. @@ -142,56 +168,14 @@ p +cell flag +cell Show help message and available arguments. -+h(2, "model") Model - +tag experimental - -p - | Initialise a new model and its data directory. For more info on this, see - | the documentation on #[+a("/docs/usage/adding-languages") adding languages]. - -+code(false, "bash"). - python -m spacy model [lang] [model_dir] [freqs_data] [clusters_data] [vectors_data] - -+table(["Argument", "Type", "Description"]) - +row - +cell #[code lang] - +cell positional - +cell Model language. - - +row - +cell #[code model_dir] - +cell positional - +cell Output directory to store the model in. - - +row - +cell #[code freqs_data] - +cell positional - +cell Tab-separated frequencies file. - - +row - +cell #[code clusters_data] - +cell positional - +cell Brown custers file (optional). - - +row - +cell #[code vectors_data] - +cell positional - +cell Word vectors file (optional). - - +row - +cell #[code --help], #[code -h] - +cell flag - +cell Show help message and available arguments. - +h(2, "train") Train - +tag experimental p | Train a model. Expects data in spaCy's | #[+a("/docs/api/annotation#json-input") JSON format]. +code(false, "bash"). - python -m spacy train [lang] [output_dir] [train_data] [dev_data] [--n_iter] [--parser_L1] [--no_tagger] [--no_parser] [--no_ner] + python -m spacy train [lang] [output_dir] [train_data] [dev_data] [--n-iter] [--n-sents] [--use-gpu] [--no-tagger] [--no-parser] [--no-entities] +table(["Argument", "Type", "Description"]) +row @@ -215,27 +199,32 @@ p +cell Location of JSON-formatted dev data (optional). +row - +cell #[code --n_iter], #[code -n] + +cell #[code --n-iter], #[code -n] +cell option - +cell Number of iterations (default: #[code 15]). + +cell Number of iterations (default: #[code 20]). +row - +cell #[code --parser_L1], #[code -L] + +cell #[code --n-sents], #[code -ns] +cell option - +cell L1 regularization penalty for parser (default: #[code 0.0]). + +cell Number of sentences (default: #[code 0]). +row - +cell #[code --no_tagger], #[code -T] + +cell #[code --use-gpu], #[code -g] + +cell option + +cell Use GPU. + + +row + +cell #[code --no-tagger], #[code -T] +cell flag +cell Don't train tagger. +row - +cell #[code --no_parser], #[code -P] + +cell #[code --no-parser], #[code -P] +cell flag +cell Don't train parser. +row - +cell #[code --no_ner], #[code -N] + +cell #[code --no-entities], #[code -N] +cell flag +cell Don't train NER. @@ -244,18 +233,117 @@ p +cell flag +cell Show help message and available arguments. ++h(3, "train-hyperparams") Environment variables for hyperparameters + +p + | spaCy lets you set hyperparameters for training via environment variables. + | This is useful, because it keeps the command simple and allows you to + | #[+a("https://askubuntu.com/questions/17536/how-do-i-create-a-permanent-bash-alias/17537#17537") create an alias] + | for your custom #[code train] command while still being able to easily + | tweak the hyperparameters. For example: + ++code(false, "bash"). + parser_hidden_depth=2 parser_maxout_pieces=1 train-parser + ++under-construction + ++table(["Name", "Description", "Default"]) + +row + +cell #[code dropout_from] + +cell + +cell #[code 0.2] + + +row + +cell #[code dropout_to] + +cell + +cell #[code 0.2] + + +row + +cell #[code dropout_decay] + +cell + +cell #[code 0.0] + + +row + +cell #[code batch_from] + +cell + +cell #[code 1] + + +row + +cell #[code batch_to] + +cell + +cell #[code 64] + + +row + +cell #[code batch_compound] + +cell + +cell #[code 1.001] + + +row + +cell #[code token_vector_width] + +cell + +cell #[code 128] + + +row + +cell #[code embed_size] + +cell + +cell #[code 7500] + + +row + +cell #[code parser_maxout_pieces] + +cell + +cell #[code 2] + + +row + +cell #[code parser_hidden_depth] + +cell + +cell #[code 1] + + +row + +cell #[code hidden_width] + +cell + +cell #[code 128] + + +row + +cell #[code learn_rate] + +cell + +cell #[code 0.001] + + +row + +cell #[code optimizer_B1] + +cell + +cell #[code 0.9] + + +row + +cell #[code optimizer_B2] + +cell + +cell #[code 0.999] + + +row + +cell #[code optimizer_eps] + +cell + +cell #[code 1e-08] + + +row + +cell #[code L2_penalty] + +cell + +cell #[code 1e-06] + + +row + +cell #[code grad_norm_clip] + +cell + +cell #[code 1.0] + +h(2, "package") Package - +tag experimental p | Generate a #[+a("/docs/usage/saving-loading#generating") model Python package] | from an existing model data directory. All data files are copied over. | If the path to a meta.json is supplied, or a meta.json is found in the | input directory, this file is used. Otherwise, the data can be entered - | directly from the command line. While this feature is still experimental, - | the required file templates are downloaded from - | #[+src(gh("spacy-dev-resources", "templates/model")) GitHub]. This means - | you need to be connected to the internet to use this command. + | directly from the command line. The required file templates are downloaded + | from #[+src(gh("spacy-dev-resources", "templates/model")) GitHub] to make + | sure you're always using the latest versions. This means you need to be + | connected to the internet to use this command. +code(false, "bash"). python -m spacy package [input_dir] [output_dir] [--meta] [--force] diff --git a/website/docs/api/dependencyparser.jade b/website/docs/api/dependencyparser.jade index 54b4774ad..a1a7e0b36 100644 --- a/website/docs/api/dependencyparser.jade +++ b/website/docs/api/dependencyparser.jade @@ -4,31 +4,7 @@ include ../../_includes/_mixins p Annotate syntactic dependencies on #[code Doc] objects. -+h(2, "load") DependencyParser.load - +tag classmethod - -p Load the statistical model from the supplied path. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code path] - +cell #[code Path] - +cell The path to load from. - - +row - +cell #[code vocab] - +cell #[code Vocab] - +cell The vocabulary. Must be shared by the documents to be processed. - - +row - +cell #[code require] - +cell bool - +cell Whether to raise an error if the files are not found. - - +footrow - +cell return - +cell #[code DependencyParser] - +cell The newly constructed object. ++under-construction +h(2, "init") DependencyParser.__init__ +tag method @@ -47,7 +23,7 @@ p Create a #[code DependencyParser]. +cell The statistical model. +footrow - +cell return + +cell returns +cell #[code DependencyParser] +cell The newly constructed object. @@ -65,7 +41,7 @@ p +cell The document to be processed. +footrow - +cell return + +cell returns +cell #[code None] +cell - @@ -93,7 +69,7 @@ p Process a stream of documents. | parallel. +footrow - +cell yield + +cell yields +cell #[code Doc] +cell Documents, in order. @@ -114,7 +90,7 @@ p Update the statistical model. +cell The gold-standard annotations, to calculate the loss. +footrow - +cell return + +cell returns +cell int +cell The loss on this example. @@ -130,6 +106,6 @@ p Set up a stepwise state, to introspect and control the transition sequence. +cell The document to step through. +footrow - +cell return + +cell returns +cell #[code StepwiseState] +cell A state object, to step through the annotation process. diff --git a/website/docs/api/displacy.jade b/website/docs/api/displacy.jade index de5707722..59fcca3ca 100644 --- a/website/docs/api/displacy.jade +++ b/website/docs/api/displacy.jade @@ -4,12 +4,13 @@ include ../../_includes/_mixins p | As of v2.0, spaCy comes with a built-in visualization suite. For more - | info and examples, see the usage workflow on + | info and examples, see the usage guide on | #[+a("/docs/usage/visualizers") visualizing spaCy]. -+h(2, "serve") serve ++h(2, "serve") displacy.serve +tag method + +tag-new(2) p | Serve a dependency parse tree or named entity visualization to view it @@ -54,14 +55,24 @@ p +cell #[+a("#options") Visualizer-specific options], e.g. colors. +cell #[code {}] + +row + +cell #[code manual] + +cell bool + +cell + | Don't parse #[code Doc] and instead, expect a dict or list of + | dicts. #[+a("/docs/usage/visualizers#manual-usage") See here] + | for formats and examples. + +cell #[code False] + +row +cell #[code port] +cell int +cell Port to serve visualization. +cell #[code 5000] -+h(2, "render") render ++h(2, "render") displacy.render +tag method + +tag-new(2) p Render a dependency parse tree or named entity visualization. @@ -111,8 +122,17 @@ p Render a dependency parse tree or named entity visualization. +cell #[+a("#options") Visualizer-specific options], e.g. colors. +cell #[code {}] + +row + +cell #[code manual] + +cell bool + +cell + | Don't parse #[code Doc] and instead, expect a dict or list of + | dicts. #[+a("/docs/usage/visualizers#manual-usage") See here] + | for formats and examples. + +cell #[code False] + +footrow - +cell return + +cell returns +cell unicode +cell Rendered HTML markup. +cell @@ -185,7 +205,7 @@ p +cell #[code arrow_spacing] +cell int +cell Spacing between arrows in px to avoid overlaps. - +cell #[code 20] + +cell #[code 20] / #[code 12] (compact) +row +cell #[code word_spacing] @@ -218,7 +238,7 @@ p +cell #[code colors] +cell dict +cell - | Color overrides. Entity types in lowercase should be mapped to + | Color overrides. Entity types in uppercase should be mapped to | color names or values. +cell #[code {}] diff --git a/website/docs/api/doc.jade b/website/docs/api/doc.jade index 77c98a6a3..929985144 100644 --- a/website/docs/api/doc.jade +++ b/website/docs/api/doc.jade @@ -18,7 +18,7 @@ p # Construction 2 from spacy.tokens import Doc - doc = doc = Doc(nlp.vocab, words=[u'hello', u'world', u'!'], + doc = Doc(nlp.vocab, words=[u'hello', u'world', u'!'], spaces=[True, False, False]) +h(2, "init") Doc.__init__ @@ -48,7 +48,7 @@ p | specified. Defaults to a sequence of #[code True]. +footrow - +cell return + +cell returns +cell #[code Doc] +cell The newly constructed object. @@ -64,7 +64,7 @@ p doc = nlp(u'Give it back! He pleaded.') assert doc[0].text == 'Give' assert doc[-1].text == '.' - span = doc[1:1] + span = doc[1:3] assert span.text == 'it back' +table(["Name", "Type", "Description"]) @@ -74,7 +74,7 @@ p +cell The index of the token. +footrow - +cell return + +cell returns +cell #[code Token] +cell The token at #[code doc[i]]. @@ -97,7 +97,7 @@ p +cell The slice of the document to get. +footrow - +cell return + +cell returns +cell #[code Span] +cell The span at #[code doc[start : end]]. @@ -109,9 +109,8 @@ p | easily accessed. +aside-code("Example"). - doc = nlp(u'Give it back! He pleaded.') - for token in doc: - print(token.text, token.tag_) + doc = nlp(u'Give it back') + assert [t.text for t in doc] == [u'Give', u'it', u'back'] p | This is the main way of accessing #[+api("token") #[code Token]] objects, @@ -122,7 +121,7 @@ p +table(["Name", "Type", "Description"]) +footrow - +cell yield + +cell yields +cell #[code Token] +cell A #[code Token] object. @@ -137,20 +136,21 @@ p Get the number of tokens in the document. +table(["Name", "Type", "Description"]) +footrow - +cell return + +cell returns +cell int +cell The number of tokens in the document. +h(2, "similarity") Doc.similarity +tag method - +tag requires model + +tag-model("vectors") p | Make a semantic similarity estimate. The default estimate is cosine | similarity using an average of word vectors. +aside-code("Example"). - apples, and, oranges = nlp(u'apples and oranges') + apples = nlp(u'I like apples') + oranges = nlp(u'I like oranges') apples_oranges = apples.similarity(oranges) oranges_apples = oranges.similarity(apples) assert apples_oranges == oranges_apples @@ -164,7 +164,7 @@ p | #[code Span], #[code Token] and #[code Lexeme] objects. +footrow - +cell return + +cell returns +cell float +cell A scalar similarity score. Higher is more similar. @@ -177,11 +177,10 @@ p | of the given attribute ID. +aside-code("Example"). - from spacy import attrs + from spacy.attrs import ORTH doc = nlp(u'apple apple orange banana') - tokens.count_by(attrs.ORTH) - # {12800L: 1, 11880L: 2, 7561L: 1} - tokens.to_array([attrs.ORTH]) + assert doc.count_by(ORTH) == {7024L: 1, 119552L: 1, 2087L: 2} + doc.to_array([attrs.ORTH]) # array([[11880], [11880], [7561], [12800]]) +table(["Name", "Type", "Description"]) @@ -191,7 +190,7 @@ p +cell The attribute ID +footrow - +cell return + +cell returns +cell dict +cell A dictionary mapping attributes to integer counts. @@ -216,7 +215,7 @@ p +cell A list of attribute ID ints. +footrow - +cell return + +cell returns +cell #[code numpy.ndarray[ndim=2, dtype='int32']] +cell | The exported attributes as a 2D numpy array, with one row per @@ -236,6 +235,7 @@ p np_array = doc.to_array([LOWER, POS, ENT_TYPE, IS_ALPHA]) doc2 = Doc(doc.vocab) doc2.from_array([LOWER, POS, ENT_TYPE, IS_ALPHA], np_array) + assert doc.text == doc2.text +table(["Name", "Type", "Description"]) +row @@ -249,10 +249,51 @@ p +cell The attribute values to load. +footrow - +cell return + +cell returns +cell #[code Doc] +cell Itself. ++h(2, "to_disk") Doc.to_disk + +tag method + +tag-new(2) + +p Save the current state to a directory. + ++aside-code("Example"). + doc.to_disk('/path/to/doc') + ++table(["Name", "Type", "Description"]) + +row + +cell #[code path] + +cell unicode or #[code Path] + +cell + | A path to a directory, which will be created if it doesn't exist. + | Paths may be either strings or #[code Path]-like objects. + ++h(2, "from_disk") Doc.from_disk + +tag method + +tag-new(2) + +p Loads state from a directory. Modifies the object in place and returns it. + ++aside-code("Example"). + from spacy.tokens import Doc + from spacy.vocab import Vocab + doc = Doc(Vocab()).from_disk('/path/to/doc') + ++table(["Name", "Type", "Description"]) + +row + +cell #[code path] + +cell unicode or #[code Path] + +cell + | A path to a directory. Paths may be either strings or + | #[code Path]-like objects. + + +footrow + +cell returns + +cell #[code Doc] + +cell The modified #[code Doc] object. + +h(2, "to_bytes") Doc.to_bytes +tag method @@ -264,7 +305,7 @@ p Serialize, i.e. export the document contents to a binary string. +table(["Name", "Type", "Description"]) +footrow - +cell return + +cell returns +cell bytes +cell | A losslessly serialized copy of the #[code Doc], including all @@ -290,9 +331,9 @@ p Deserialize, i.e. import the document contents from a binary string. +cell The string to load from. +footrow - +cell return + +cell returns +cell #[code Doc] - +cell Itself. + +cell The #[code Doc] object. +h(2, "merge") Doc.merge +tag method @@ -306,8 +347,7 @@ p +aside-code("Example"). doc = nlp(u'Los Angeles start.') doc.merge(0, len('Los Angeles'), 'NNP', 'Los Angeles', 'GPE') - print([token.text for token in doc]) - # ['Los Angeles', 'start', '.'] + assert [t.text for t in doc] == [u'Los Angeles', u'start', u'.'] +table(["Name", "Type", "Description"]) +row @@ -329,7 +369,7 @@ p | the span. +footrow - +cell return + +cell returns +cell #[code Token] +cell | The newly merged token, or #[code None] if the start and end @@ -337,7 +377,7 @@ p +h(2, "print_tree") Doc.print_tree +tag method - +tag requires model + +tag-model("parse") p | Returns the parse trees in JSON (dict) format. Especially useful for @@ -364,42 +404,13 @@ p +cell Don't include arcs or modifiers. +footrow - +cell return + +cell returns +cell dict +cell Parse tree as dict. -+h(2, "text") Doc.text - +tag property - -p A unicode representation of the document text. - -+aside-code("Example"). - text = u'Give it back! He pleaded.' - doc = nlp(text) - assert doc.text == text - -+table(["Name", "Type", "Description"]) - +footrow - +cell return - +cell unicode - +cell The original verbatim text of the document. - -+h(2, "text_with_ws") Doc.text_with_ws - +tag property - -p - | An alias of #[code Doc.text], provided for duck-type compatibility with - | #[code Span] and #[code Token]. - -+table(["Name", "Type", "Description"]) - +footrow - +cell return - +cell unicode - +cell The original verbatim text of the document. - +h(2, "ents") Doc.ents +tag property - +tag requires model + +tag-model("NER") p | Iterate over the entities in the document. Yields named-entity @@ -415,13 +426,13 @@ p +table(["Name", "Type", "Description"]) +footrow - +cell yield + +cell yields +cell #[code Span] +cell Entities in the document. +h(2, "noun_chunks") Doc.noun_chunks +tag property - +tag requires model + +tag-model("parse") p | Iterate over the base noun phrases in the document. Yields base @@ -438,13 +449,13 @@ p +table(["Name", "Type", "Description"]) +footrow - +cell yield + +cell yields +cell #[code Span] +cell Noun chunks in the document. +h(2, "sents") Doc.sents +tag property - +tag requires model + +tag-model("parse") p | Iterate over the sentences in the document. Sentence spans have no label. @@ -460,50 +471,82 @@ p +table(["Name", "Type", "Description"]) +footrow - +cell yield + +cell yields +cell #[code Span] +cell Sentences in the document. +h(2, "has_vector") Doc.has_vector +tag property - +tag requires model + +tag-model("vectors") p | A boolean value indicating whether a word vector is associated with the | object. +aside-code("Example"). - apple = nlp(u'apple') - assert apple.has_vector + doc = nlp(u'I like apples') + assert doc.has_vector +table(["Name", "Type", "Description"]) +footrow - +cell return + +cell returns +cell bool +cell Whether the document has a vector data attached. +h(2, "vector") Doc.vector +tag property - +tag requires model + +tag-model("vectors") p | A real-valued meaning representation. Defaults to an average of the | token vectors. +aside-code("Example"). - apple = nlp(u'apple') - (apple.vector.dtype, apple.vector.shape) - # (dtype('float32'), (300,)) + apples = nlp(u'I like apples') + assert doc.vector.dtype == 'float32' + assert doc.vector.shape == (300,) +table(["Name", "Type", "Description"]) +footrow - +cell return + +cell returns +cell #[code numpy.ndarray[ndim=1, dtype='float32']] +cell A 1D numpy array representing the document's semantics. ++h(2, "vector_norm") Doc.vector_norm + +tag property + +tag-model("vectors") + +p + | The L2 norm of the document's vector representation. + ++aside-code("Example"). + doc1 = nlp(u'I like apples') + doc2 = nlp(u'I like oranges') + doc1.vector_norm # 4.54232424414368 + doc2.vector_norm # 3.304373298575751 + assert doc1.vector_norm != doc2.vector_norm + ++table(["Name", "Type", "Description"]) + +footrow + +cell returns + +cell float + +cell The L2 norm of the vector representation. + +h(2, "attributes") Attributes +table(["Name", "Type", "Description"]) + +row + +cell #[code text] + +cell unicode + +cell A unicode representation of the document text. + + +row + +cell #[code text_with_ws] + +cell unicode + +cell + | An alias of #[code Doc.text], provided for duck-type compatibility + | with #[code Span] and #[code Token]. + +row +cell #[code mem] +cell #[code Pool] @@ -514,6 +557,21 @@ p +cell #[code Vocab] +cell The store of lexical types. + +row + +cell #[code tensor] #[+tag-new(2)] + +cell object + +cell Container for dense vector representations. + + +row + +cell #[code cats] #[+tag-new(2)] + +cell dictionary + +cell + | Maps either a label to a score for categories applied to whole + | document, or #[code (start_char, end_char, label)] to score for + | categories applied to spans. #[code start_char] and #[code end_char] + | should be character offsets, label can be either a string or an + | integer ID, and score should be a float. + +row +cell #[code user_data] +cell - diff --git a/website/docs/api/entityrecognizer.jade b/website/docs/api/entityrecognizer.jade index 2f4780bad..e3775b7f4 100644 --- a/website/docs/api/entityrecognizer.jade +++ b/website/docs/api/entityrecognizer.jade @@ -4,31 +4,7 @@ include ../../_includes/_mixins p Annotate named entities on #[code Doc] objects. -+h(2, "load") EntityRecognizer.load - +tag classmethod - -p Load the statistical model from the supplied path. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code path] - +cell #[code Path] - +cell The path to load from. - - +row - +cell #[code vocab] - +cell #[code Vocab] - +cell The vocabulary. Must be shared by the documents to be processed. - - +row - +cell #[code require] - +cell bool - +cell Whether to raise an error if the files are not found. - - +footrow - +cell return - +cell #[code EntityRecognizer] - +cell The newly constructed object. ++under-construction +h(2, "init") EntityRecognizer.__init__ +tag method @@ -47,7 +23,7 @@ p Create an #[code EntityRecognizer]. +cell The statistical model. +footrow - +cell return + +cell returns +cell #[code EntityRecognizer] +cell The newly constructed object. @@ -63,7 +39,7 @@ p Apply the entity recognizer, setting the NER tags onto the #[code Doc] object. +cell The document to be processed. +footrow - +cell return + +cell returns +cell #[code None] +cell - @@ -91,7 +67,7 @@ p Process a stream of documents. | parallel. +footrow - +cell yield + +cell yields +cell #[code Doc] +cell Documents, in order. @@ -112,7 +88,7 @@ p Update the statistical model. +cell The gold-standard annotations, to calculate the loss. +footrow - +cell return + +cell returns +cell int +cell The loss on this example. @@ -128,6 +104,6 @@ p Set up a stepwise state, to introspect and control the transition sequence. +cell The document to step through. +footrow - +cell return + +cell returns +cell #[code StepwiseState] +cell A state object, to step through the annotation process. diff --git a/website/docs/api/features.jade b/website/docs/api/features.jade deleted file mode 100644 index 018790145..000000000 --- a/website/docs/api/features.jade +++ /dev/null @@ -1,138 +0,0 @@ -//- 💫 DOCS > API > LINEAR MOEL FEATURES - -include ../../_includes/_mixins - -p - | There are two popular strategies for putting together machine learning - | models for NLP: sparse linear models, and neural networks. To solve NLP - | problems with linear models, feature templates need to be assembled that - | combine multiple atomic predictors. This page documents the atomic - | predictors used in the spaCy 1.0 #[+api("parser") #[code Parser]], - | #[+api("tagger") #[code Tagger]] and - | #[+api("entityrecognizer") #[code EntityRecognizer]]. - -p - | To understand the scheme, recall that spaCy's #[code Parser] and - | #[code EntityRecognizer] are implemented as push-down automata. They - | maintain a "stack" that holds the current entity, and a "buffer" - | consisting of the words to be processed. - -p - | Each state consists of the words on the stack (if any), which consistute - | the current entity being constructed. We also have the current word, and - | the two subsequent words. Finally, we also have the entities previously - | built. - -p - | This gives us a number of tokens to ask questions about, to make the - | features. About each of these tokens, we can ask about a number of - | different properties. Each feature identifier asks about a specific - | property of a specific token of the context. - -+h(2, "tokens") Context tokens - -+table([ "ID", "Description" ]) - +row - +cell #[code S0] - +cell - | The first word on the stack, i.e. the token most recently added - | to the current entity. - - +row - +cell #[code S1] - +cell The second word on the stack, i.e. the second most recently added. - - +row - +cell #[code S2] - +cell The third word on the stack, i.e. the third most recently added. - - +row - +cell #[code N0] - +cell The first word of the buffer, i.e. the current word being tagged. - - +row - +cell #[code N1] - +cell The second word of the buffer. - - +row - +cell #[code N2] - +cell The third word of the buffer. - - +row - +cell #[code P1] - +cell The word immediately before #[code N0]. - - +row - +cell #[code P2] - +cell The second word before #[code N0]. - - +row - +cell #[code E0] - +cell The first word of the previously constructed entity. - - +row - +cell #[code E1] - +cell The first word of the second previously constructed entity. - -p About each of these tokens, we can ask: - -+table([ "ID", "Attribute", "Description" ]) - +row - +cell #[code N0w] - +cell #[code token.orth] - +cell The word form. - - +row - +cell #[code N0W] - +cell #[code token.lemma] - +cell The word's lemma. - - +row - +cell #[code N0p] - +cell #[code token.tag] - +cell The word's (full) POS tag. - - +row - +cell #[code N0c] - +cell #[code token.cluster] - +cell The word's (full) Brown cluster. - - +row - +cell #[code N0c4] - +cell - - +cell First four digit prefix of the word's Brown cluster. - - +row - +cell #[code N0c6] - +cell - - +cell First six digit prefix of the word's Brown cluster. - - +row - +cell #[code N0L] - +cell - - +cell The word's dependency label. Not used as a feature in the NER. - - +row - +cell #[code N0_prefix] - +cell #[code token.prefix] - +cell The first three characters of the word. - - +row - +cell #[code N0_suffix] - +cell #[code token.suffix] - +cell The last three characters of the word. - - +row - +cell #[code N0_shape] - +cell #[code token.shape] - +cell The word's shape, i.e. is it alphabetic, numeric, etc. - - +row - +cell #[code N0_ne_iob] - +cell #[code token.ent_iob] - +cell The Inside/Outside/Begin code of the word's NER tag. - - +row - +cell #[code N0_ne_type] - +cell #[code token.ent_type] - +cell The word's NER type. diff --git a/website/docs/api/goldcorpus.jade b/website/docs/api/goldcorpus.jade new file mode 100644 index 000000000..3b3d92823 --- /dev/null +++ b/website/docs/api/goldcorpus.jade @@ -0,0 +1,24 @@ +//- 💫 DOCS > API > GOLDCORPUS + +include ../../_includes/_mixins + +p + | An annotated corpus, using the JSON file format. Manages annotations for + | tagging, dependency parsing and NER. + ++h(2, "init") GoldCorpus.__init__ + +tag method + +tag-new(2) + +p Create a #[code GoldCorpus]. + ++table(["Name", "Type", "Description"]) + +row + +cell #[code train_path] + +cell unicode or #[code Path] + +cell File or directory of training data. + + +row + +cell #[code dev_path] + +cell unicode or #[code Path] + +cell File or directory of development data. diff --git a/website/docs/api/goldparse.jade b/website/docs/api/goldparse.jade index 8ddfce3da..03118343d 100644 --- a/website/docs/api/goldparse.jade +++ b/website/docs/api/goldparse.jade @@ -4,6 +4,72 @@ include ../../_includes/_mixins p Collection for training annotations. ++h(2, "init") GoldParse.__init__ + +tag method + +p Create a #[code GoldParse]. + ++table(["Name", "Type", "Description"]) + +row + +cell #[code doc] + +cell #[code Doc] + +cell The document the annotations refer to. + + +row + +cell #[code words] + +cell iterable + +cell A sequence of unicode word strings. + + +row + +cell #[code tags] + +cell iterable + +cell A sequence of strings, representing tag annotations. + + +row + +cell #[code heads] + +cell iterable + +cell A sequence of integers, representing syntactic head offsets. + + +row + +cell #[code deps] + +cell iterable + +cell A sequence of strings, representing the syntactic relation types. + + +row + +cell #[code entities] + +cell iterable + +cell A sequence of named entity annotations, either as BILUO tag strings, or as #[code (start_char, end_char, label)] tuples, representing the entity positions. + + +footrow + +cell returns + +cell #[code GoldParse] + +cell The newly constructed object. + ++h(2, "len") GoldParse.__len__ + +tag method + +p Get the number of gold-standard tokens. + ++table(["Name", "Type", "Description"]) + +footrow + +cell returns + +cell int + +cell The number of gold-standard tokens. + ++h(2, "is_projective") GoldParse.is_projective + +tag property + +p + | Whether the provided syntactic annotations form a projective dependency + | tree. + ++table(["Name", "Type", "Description"]) + +footrow + +cell returns + +cell bool + +cell Whether annotations form projective tree. + + +h(2, "attributes") Attributes +table(["Name", "Type", "Description"]) @@ -37,67 +103,65 @@ p Collection for training annotations. +cell list +cell The alignment from gold tokenization to candidate tokenization. -+h(2, "init") GoldParse.__init__ - +tag method + +row + +cell #[code cats] #[+tag-new(2)] + +cell list + +cell + | Entries in the list should be either a label, or a + | #[code (start, end, label)] triple. The tuple form is used for + | categories applied to spans of the document. -p Create a GoldParse. + ++h(2, "util") Utilities + ++h(3, "biluo_tags_from_offsets") gold.biluo_tags_from_offsets + +tag function + +p + | Encode labelled spans into per-token tags, using the + | #[+a("/docs/api/annotation#biluo") BILUO scheme] (Begin/In/Last/Unit/Out). + +p + | Returns a list of unicode strings, describing the tags. Each tag string + | will be of the form either #[code ""], #[code "O"] or + | #[code "{action}-{label}"], where action is one of #[code "B"], + | #[code "I"], #[code "L"], #[code "U"]. The string #[code "-"] + | is used where the entity offsets don't align with the tokenization in the + | #[code Doc] object. The training algorithm will view these as missing + | values. #[code O] denotes a non-entity token. #[code B] denotes the + | beginning of a multi-token entity, #[code I] the inside of an entity + | of three or more tokens, and #[code L] the end of an entity of two or + | more tokens. #[code U] denotes a single-token entity. + ++aside-code("Example"). + from spacy.gold import biluo_tags_from_offsets + text = 'I like London.' + entities = [(len('I like '), len('I like London'), 'LOC')] + doc = tokenizer(text) + tags = biluo_tags_from_offsets(doc, entities) + assert tags == ['O', 'O', 'U-LOC', 'O'] +table(["Name", "Type", "Description"]) +row +cell #[code doc] +cell #[code Doc] - +cell The document the annotations refer to. - - +row - +cell #[code words] - +cell - - +cell A sequence of unicode word strings. - - +row - +cell #[code tags] - +cell - - +cell A sequence of strings, representing tag annotations. - - +row - +cell #[code heads] - +cell - - +cell A sequence of integers, representing syntactic head offsets. - - +row - +cell #[code deps] - +cell - - +cell A sequence of strings, representing the syntactic relation types. + +cell + | The document that the entity offsets refer to. The output tags + | will refer to the token boundaries within the document. +row +cell #[code entities] - +cell - - +cell A sequence of named entity annotations, either as BILUO tag strings, or as #[code (start_char, end_char, label)] tuples, representing the entity positions. + +cell iterable + +cell + | A sequence of #[code (start, end, label)] triples. #[code start] + | and #[code end] should be character-offset integers denoting the + | slice into the original string. +footrow - +cell return - +cell #[code GoldParse] - +cell The newly constructed object. + +cell returns + +cell list + +cell + | Unicode strings, describing the + | #[+a("/docs/api/annotation#biluo") BILUO] tags. -+h(2, "len") GoldParse.__len__ - +tag method -p Get the number of gold-standard tokens. - -+table(["Name", "Type", "Description"]) - +footrow - +cell return - +cell int - +cell The number of gold-standard tokens. - -+h(2, "is_projective") GoldParse.is_projective - +tag property - -p - | Whether the provided syntactic annotations form a projective dependency - | tree. - -+table(["Name", "Type", "Description"]) - +footrow - +cell return - +cell bool - +cell Whether annotations form projective tree. diff --git a/website/docs/api/index.jade b/website/docs/api/index.jade index 24f3d4458..f92080975 100644 --- a/website/docs/api/index.jade +++ b/website/docs/api/index.jade @@ -2,6 +2,8 @@ include ../../_includes/_mixins ++under-construction + +h(2, "comparison") Feature comparison p diff --git a/website/docs/api/language-models.jade b/website/docs/api/language-models.jade index 0990de358..74007f228 100644 --- a/website/docs/api/language-models.jade +++ b/website/docs/api/language-models.jade @@ -2,7 +2,10 @@ include ../../_includes/_mixins -p spaCy currently supports the following languages and capabilities: +p + | spaCy currently provides models for the following languages and + | capabilities: + +aside-code("Download language models", "bash"). python -m spacy download en @@ -22,12 +25,16 @@ p spaCy currently supports the following languages and capabilities: +row +cell French #[code fr] - each icon in [ "pro", "pro", "con", "pro", "con", "pro", "pro", "con" ] + each icon in [ "pro", "con", "con", "pro", "con", "pro", "pro", "con" ] +cell.u-text-center #[+procon(icon)] -+h(2, "available") Available models + +row + +cell Spanish #[code es] + each icon in [ "pro", "pro", "con", "pro", "pro", "pro", "pro", "con" ] + +cell.u-text-center #[+procon(icon)] -include ../usage/_models-list +p + +button("/docs/usage/models", true, "primary") See available models +h(2, "alpha-support") Alpha tokenization support @@ -52,9 +59,35 @@ p | #[+a("https://github.com/mocobeta/janome") Janome]. +table([ "Language", "Code", "Source" ]) - each language, code in { es: "Spanish", it: "Italian", pt: "Portuguese", nl: "Dutch", sv: "Swedish", fi: "Finnish", nb: "Norwegian Bokmål", da: "Danish", hu: "Hungarian", pl: "Polish", bn: "Bengali", he: "Hebrew", zh: "Chinese", ja: "Japanese" } + each language, code in { it: "Italian", pt: "Portuguese", nl: "Dutch", sv: "Swedish", fi: "Finnish", nb: "Norwegian Bokmål", da: "Danish", hu: "Hungarian", pl: "Polish", bn: "Bengali", he: "Hebrew", zh: "Chinese", ja: "Japanese" } +row +cell #{language} +cell #[code=code] +cell +src(gh("spaCy", "spacy/lang/" + code)) lang/#{code} + ++h(2, "multi-language") Multi-language support + +tag-new(2) + +p + | As of v2.0, spaCy supports models trained on more than one language. This + | is especially useful for named entity recognition. The language ID used + | for multi-language or language-neutral models is #[code xx]. The + | language class, a generic subclass containing only the base language data, + | can be found in #[+src(gh("spaCy", "spacy/lang/xx")) lang/xx]. + +p + | To load your model with the neutral, multi-language class, simply set + | #[code "language": "xx"] in your + | #[+a("/docs/usage/saving-loading#models-generating") model package]'s + | meta.json. You can also import the class directly, or call + | #[+api("util#get_lang_class") #[code util.get_lang_class()]] for + | lazy-loading. + ++code("Standard import"). + from spacy.lang.xx import MultiLanguage + nlp = MultiLanguage() + ++code("With lazy-loading"). + from spacy.util import get_lang_class + nlp = get_lang_class('xx') diff --git a/website/docs/api/language.jade b/website/docs/api/language.jade index d7090c870..9c26f506c 100644 --- a/website/docs/api/language.jade +++ b/website/docs/api/language.jade @@ -2,79 +2,69 @@ include ../../_includes/_mixins -p A text processing pipeline. +p + | A text-processing pipeline. Usually you'll load this once per process, + | and pass the instance around your application. -+h(2, "attributes") Attributes ++h(2, "init") Language.__init__ + +tag method + +p Initialise a #[code Language] object. + ++aside-code("Example"). + from spacy.language import Language + nlp = Language(pipeline=['token_vectors', 'tags', + 'dependencies']) + + from spacy.lang.en import English + nlp = English() +table(["Name", "Type", "Description"]) +row +cell #[code vocab] +cell #[code Vocab] - +cell A container for the lexical types. - - +row - +cell #[code tokenizer] - +cell #[code Tokenizer] - +cell Find word boundaries and create #[code Doc] object. - - +row - +cell #[code tagger] - +cell #[code Tagger] - +cell Annotate #[code Doc] objects with POS tags. - - +row - +cell #[code parser] - +cell #[code DependencyParser] - +cell Annotate #[code Doc] objects with syntactic dependencies. - - +row - +cell #[code entity] - +cell #[code EntityRecognizer] - +cell Annotate #[code Doc] objects with named entities. - - +row - +cell #[code matcher] - +cell #[code Matcher] - +cell Rule-based sequence matcher. + +cell + | A #[code Vocab] object. If #[code True], a vocab is created via + | #[code Language.Defaults.create_vocab]. +row +cell #[code make_doc] - +cell #[code lambda text: Doc] - +cell Create a #[code Doc] object from unicode text. + +cell callable + +cell + | A function that takes text and returns a #[code Doc] object. + | Usually a #[code Tokenizer]. +row +cell #[code pipeline] - +cell - - +cell Sequence of annotation functions. + +cell list + +cell + | A list of annotation processes or IDs of annotation, processes, + | e.g. a #[code Tagger] object, or #[code 'tagger']. IDs are looked + | up in #[code Language.Defaults.factories]. - -+h(2, "init") Language.__init__ - +tag method - -p Create or load the pipeline. - -+table(["Name", "Type", "Description"]) +row - +cell #[code **overrides] - +cell - - +cell Keyword arguments indicating which defaults to override. + +cell #[code meta] + +cell dict + +cell + | Custom meta data for the #[code Language] class. Is written to by + | models to add model meta data. +footrow - +cell return + +cell returns +cell #[code Language] +cell The newly constructed object. +h(2, "call") Language.__call__ +tag method -p Apply the pipeline to a single text. +p + | Apply the pipeline to some text. The text can span multiple sentences, + | and can contain arbtrary whitespace. Alignment into the original string + | is preserved. +aside-code("Example"). - from spacy.en import English - nlp = English() - doc = nlp('An example sentence. Another example sentence.') - doc[0].orth_, doc[0].head.tag_ - # ('An', 'NN') + doc = nlp(u'An example sentence. Another sentence.') + assert (doc[0].text, doc[0].head.tag_) == ('An', 'NN') +table(["Name", "Type", "Description"]) +row @@ -83,24 +73,25 @@ p Apply the pipeline to a single text. +cell The text to be processed. +row - +cell #[code tag] - +cell bool - +cell Whether to apply the part-of-speech tagger. - - +row - +cell #[code parse] - +cell bool - +cell Whether to apply the syntactic dependency parser. - - +row - +cell #[code entity] - +cell bool - +cell Whether to apply the named entity recognizer. + +cell #[code disable] + +cell list + +cell + | Names of pipeline components to + | #[+a("/docs/usage/language-processing-pipeline#disabling") disable]. +footrow - +cell return + +cell returns +cell #[code Doc] - +cell A container for accessing the linguistic annotations. + +cell A container for accessing the annotations. + ++infobox("⚠️ Deprecation note") + .o-block + | Pipeline components to prevent from being loaded can now be added as + | a list to #[code disable], instead of specifying one keyword argument + | per component. + + +code-new doc = nlp(u"I don't want parsed", disable=['parser']) + +code-old doc = nlp(u"I don't want parsed", parse=False) +h(2, "pipe") Language.pipe +tag method @@ -132,23 +123,296 @@ p +cell int +cell The number of texts to buffer. - +footrow - +cell yield - +cell #[code Doc] - +cell Containers for accessing the linguistic annotations. + +row + +cell #[code disable] + +cell list + +cell + | Names of pipeline components to + | #[+a("/docs/usage/language-processing-pipeline#disabling") disable]. -+h(2, "save_to_directory") Language.save_to_directory + +footrow + +cell yields + +cell #[code Doc] + +cell Documents in the order of the original text. + ++h(2, "update") Language.update +tag method -p Save the #[code Vocab], #[code StringStore] and pipeline to a directory. +p Update the models in the pipeline. + ++aside-code("Example"). + for raw_text, entity_offsets in train_data: + doc = nlp.make_doc(raw_text) + gold = GoldParse(doc, entities=entity_offsets) + nlp.update([doc], [gold], drop=0.5, sgd=optimizer) + ++table(["Name", "Type", "Description"]) + +row + +cell #[code docs] + +cell iterable + +cell A batch of #[code Doc] objects. + + +row + +cell #[code golds] + +cell iterable + +cell A batch of #[code GoldParse] objects. + + +row + +cell #[code drop] + +cell float + +cell The dropout rate. + + +row + +cell #[code sgd] + +cell callable + +cell An optimizer. + + +footrow + +cell returns + +cell dict + +cell Results from the update. + ++h(2, "begin_training") Language.begin_training + +tag method + +p + | Allocate models, pre-process training data and acquire an optimizer. + ++aside-code("Example"). + optimizer = nlp.begin_training(gold_tuples) + ++table(["Name", "Type", "Description"]) + +row + +cell #[code gold_tuples] + +cell iterable + +cell Gold-standard training data. + + +row + +cell #[code **cfg] + +cell - + +cell Config parameters. + + +footrow + +cell yields + +cell tuple + +cell An optimizer. + ++h(2, "use_params") Language.use_params + +tag contextmanager + +tag method + +p + | Replace weights of models in the pipeline with those provided in the + | params dictionary. Can be used as a contextmanager, in which case, models + | go back to their original weights after the block. + ++aside-code("Example"). + with nlp.use_params(optimizer.averages): + nlp.to_disk('/tmp/checkpoint') + ++table(["Name", "Type", "Description"]) + +row + +cell #[code params] + +cell dict + +cell A dictionary of parameters keyed by model ID. + + +row + +cell #[code **cfg] + +cell - + +cell Config parameters. + ++h(2, "preprocess_gold") Language.preprocess_gold + +p + | Can be called before training to pre-process gold data. By default, it + | handles nonprojectivity and adds missing tags to the tag map. + + ++table(["Name", "Type", "Description"]) + +row + +cell #[code docs_golds] + +cell iterable + +cell Tuples of #[code Doc] and #[code GoldParse] objects. + + +footrow + +cell yields + +cell tuple + +cell Tuples of #[code Doc] and #[code GoldParse] objects. + ++h(2, "to_disk") Language.to_disk + +tag method + +tag-new(2) + +p + | Save the current state to a directory. If a model is loaded, this will + | #[strong include the model]. + ++aside-code("Example"). + nlp.to_disk('/path/to/models') +table(["Name", "Type", "Description"]) +row +cell #[code path] - +cell string or pathlib path - +cell Path to save the model. + +cell unicode or #[code Path] + +cell + | A path to a directory, which will be created if it doesn't exist. + | Paths may be either strings or #[code Path]-like objects. + + +row + +cell #[code disable] + +cell list + +cell + | Names of pipeline components to + | #[+a("/docs/usage/language-processing-pipeline#disabling") disable] + | and prevent from being saved. + ++h(2, "from_disk") Language.from_disk + +tag method + +tag-new(2) + +p + | Loads state from a directory. Modifies the object in place and returns + | it. If the saved #[code Language] object contains a model, the + | #[strong model will be loaded]. + ++aside-code("Example"). + from spacy.language import Language + nlp = Language().from_disk('/path/to/models') + ++table(["Name", "Type", "Description"]) + +row + +cell #[code path] + +cell unicode or #[code Path] + +cell + | A path to a directory. Paths may be either strings or + | #[code Path]-like objects. + + +row + +cell #[code disable] + +cell list + +cell + | Names of pipeline components to + | #[+a("/docs/usage/language-processing-pipeline#disabling") disable]. +footrow - +cell return - +cell #[code None] - +cell - + +cell returns + +cell #[code Language] + +cell The modified #[code Language] object. + ++infobox("⚠️ Deprecation note") + .o-block + | As of spaCy v2.0, the #[code save_to_directory] method has been + | renamed to #[code to_disk], to improve consistency across classes. + | Pipeline components to prevent from being loaded can now be added as + | a list to #[code disable], instead of specifying one keyword argument + | per component. + + +code-new nlp = English().from_disk(disable=['tagger', 'ner']) + +code-old nlp = spacy.load('en', tagger=False, entity=False) + ++h(2, "to_bytes") Language.to_bytes + +tag method + +p Serialize the current state to a binary string. + ++aside-code("Example"). + nlp_bytes = nlp.to_bytes() + ++table(["Name", "Type", "Description"]) + +row + +cell #[code disable] + +cell list + +cell + | Names of pipeline components to + | #[+a("/docs/usage/language-processing-pipeline#disabling") disable] + | and prevent from being serialized. + + +footrow + +cell returns + +cell bytes + +cell The serialized form of the #[code Language] object. + ++h(2, "from_bytes") Language.from_bytes + +tag method + +p Load state from a binary string. + ++aside-code("Example"). + fron spacy.lang.en import English + nlp_bytes = nlp.to_bytes() + nlp2 = English() + nlp2.from_bytes(nlp_bytes) + ++table(["Name", "Type", "Description"]) + +row + +cell #[code bytes_data] + +cell bytes + +cell The data to load from. + + +row + +cell #[code disable] + +cell list + +cell + | Names of pipeline components to + | #[+a("/docs/usage/language-processing-pipeline#disabling") disable]. + + +footrow + +cell returns + +cell #[code Language] + +cell The #[code Language] object. + ++infobox("⚠️ Deprecation note") + .o-block + | Pipeline components to prevent from being loaded can now be added as + | a list to #[code disable], instead of specifying one keyword argument + | per component. + + +code-new nlp = English().from_bytes(bytes, disable=['tagger', 'ner']) + +code-old nlp = English().from_bytes('en', tagger=False, entity=False) + ++h(2, "attributes") Attributes + ++table(["Name", "Type", "Description"]) + +row + +cell #[code vocab] + +cell #[code Vocab] + +cell A container for the lexical types. + + +row + +cell #[code tokenizer] + +cell #[code Tokenizer] + +cell The tokenizer. + + +row + +cell #[code make_doc] + +cell #[code lambda text: Doc] + +cell Create a #[code Doc] object from unicode text. + + +row + +cell #[code pipeline] + +cell list + +cell Sequence of annotation functions. + + +row + +cell #[code meta] + +cell dict + +cell + | Custom meta data for the Language class. If a model is loaded, + | contains meta data of the model. + ++h(2, "class-attributes") Class attributes + ++table(["Name", "Type", "Description"]) + +row + +cell #[code Defaults] + +cell class + +cell + | Settings, data and factory methods for creating the + | #[code nlp] object and processing pipeline. + + +row + +cell #[code lang] + +cell unicode + +cell + | Two-letter language ID, i.e. + | #[+a("https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes") ISO code]. diff --git a/website/docs/api/lexeme.jade b/website/docs/api/lexeme.jade index 9ba14e1f0..a0487be9b 100644 --- a/website/docs/api/lexeme.jade +++ b/website/docs/api/lexeme.jade @@ -2,7 +2,154 @@ include ../../_includes/_mixins -p An entry in the vocabulary. +p + | An entry in the vocabulary. A #[code Lexeme] has no string context – it's + | a word type, as opposed to a word token. It therefore has no + | part-of-speech tag, dependency parse, or lemma (if lemmatization depends + | on the part-of-speech tag). + ++h(2, "init") Lexeme.__init__ + +tag method + +p Create a #[code Lexeme] object. + ++table(["Name", "Type", "Description"]) + +row + +cell #[code vocab] + +cell #[code Vocab] + +cell The parent vocabulary. + + +row + +cell #[code orth] + +cell int + +cell The orth id of the lexeme. + + +footrow + +cell returns + +cell #[code Lexeme] + +cell The newly constructed object. + ++h(2, "set_flag") Lexeme.set_flag + +tag method + +p Change the value of a boolean flag. + ++aside-code("Example"). + COOL_FLAG = nlp.vocab.add_flag(lambda text: False) + nlp.vocab[u'spaCy'].set_flag(COOL_FLAG, True) + ++table(["Name", "Type", "Description"]) + +row + +cell #[code flag_id] + +cell int + +cell The attribute ID of the flag to set. + + +row + +cell #[code value] + +cell bool + +cell The new value of the flag. + ++h(2, "check_flag") Lexeme.check_flag + +tag method + +p Check the value of a boolean flag. + ++aside-code("Example"). + is_my_library = lambda text: text in ['spaCy', 'Thinc'] + MY_LIBRARY = nlp.vocab.add_flag(is_my_library) + assert nlp.vocab[u'spaCy'].check_flag(MY_LIBRARY) == True + ++table(["Name", "Type", "Description"]) + +row + +cell #[code flag_id] + +cell int + +cell The attribute ID of the flag to query. + + +footrow + +cell returns + +cell bool + +cell The value of the flag. + ++h(2, "similarity") Lexeme.similarity + +tag method + +tag-model("vectors") + +p Compute a semantic similarity estimate. Defaults to cosine over vectors. + ++aside-code("Example"). + apple = nlp.vocab[u'apple'] + orange = nlp.vocab[u'orange'] + apple_orange = apple.similarity(orange) + orange_apple = orange.similarity(apple) + assert apple_orange == orange_apple + ++table(["Name", "Type", "Description"]) + +row + +cell other + +cell - + +cell + | The object to compare with. By default, accepts #[code Doc], + | #[code Span], #[code Token] and #[code Lexeme] objects. + + +footrow + +cell returns + +cell float + +cell A scalar similarity score. Higher is more similar. + + ++h(2, "has_vector") Lexeme.has_vector + +tag property + +tag-model("vectors") + +p + | A boolean value indicating whether a word vector is associated with the + | lexeme. + ++aside-code("Example"). + apple = nlp.vocab[u'apple'] + assert apple.has_vector + ++table(["Name", "Type", "Description"]) + +footrow + +cell returns + +cell bool + +cell Whether the lexeme has a vector data attached. + ++h(2, "vector") Lexeme.vector + +tag property + +tag-model("vectors") + +p A real-valued meaning representation. + ++aside-code("Example"). + apple = nlp.vocab[u'apple'] + assert apple.vector.dtype == 'float32' + assert apple.vector.shape == (300,) + ++table(["Name", "Type", "Description"]) + +footrow + +cell returns + +cell #[code numpy.ndarray[ndim=1, dtype='float32']] + +cell A 1D numpy array representing the lexeme's semantics. + ++h(2, "vector_norm") Lexeme.vector_norm + +tag property + +tag-model("vectors") + +p The L2 norm of the lexeme's vector representation. + ++aside-code("Example"). + apple = nlp.vocab[u'apple'] + pasta = nlp.vocab[u'pasta'] + apple.vector_norm # 7.1346845626831055 + pasta.vector_norm # 7.759851932525635 + assert apple.vector_norm != pasta.vector_norm + ++table(["Name", "Type", "Description"]) + +footrow + +cell returns + +cell float + +cell The L2 norm of the vector representation. +h(2, "attributes") Attributes @@ -12,6 +159,16 @@ p An entry in the vocabulary. +cell #[code Vocab] +cell + +row + +cell #[code text] + +cell unicode + +cell Verbatim text content. + + +row + +cell #[code lex_id] + +cell int + +cell ID of the lexeme's lexical type. + +row +cell #[code lower] +cell int @@ -55,62 +212,74 @@ p An entry in the vocabulary. +row +cell #[code is_alpha] +cell bool - +cell Equivalent to #[code word.orth_.isalpha()]. + +cell + | Does the lexeme consist of alphabetic characters? Equivalent to + | #[code lexeme.text.isalpha()]. +row +cell #[code is_ascii] +cell bool - +cell Equivalent to #[code [any(ord(c) >= 128 for c in word.orth_)]]. + +cell + | Does the lexeme consist of ASCII characters? Equivalent to + | #[code [any(ord(c) >= 128 for c in lexeme.text)]]. +row +cell #[code is_digit] +cell bool - +cell Equivalent to #[code word.orth_.isdigit()]. + +cell + | Does the lexeme consist of digits? Equivalent to + | #[code lexeme.text.isdigit()]. +row +cell #[code is_lower] +cell bool - +cell Equivalent to #[code word.orth_.islower()]. + +cell + | Is the lexeme in lowercase? Equivalent to + | #[code lexeme.text.islower()]. +row +cell #[code is_title] +cell bool - +cell Equivalent to #[code word.orth_.istitle()]. + +cell + | Is the lexeme in titlecase? Equivalent to + | #[code lexeme.text.istitle()]. +row +cell #[code is_punct] +cell bool - +cell Equivalent to #[code word.orth_.ispunct()]. + +cell Is the lexeme punctuation? +row +cell #[code is_space] +cell bool - +cell Equivalent to #[code word.orth_.isspace()]. + +cell + | Does the lexeme consist of whitespace characters? Equivalent to + | #[code lexeme.text.isspace()]. +row +cell #[code like_url] +cell bool - +cell Does the word resemble a URL? + +cell Does the lexeme resemble a URL? +row +cell #[code like_num] +cell bool - +cell Does the word represent a number? e.g. “10.9”, “10”, “ten”, etc. + +cell Does the lexeme represent a number? e.g. "10.9", "10", "ten", etc. +row +cell #[code like_email] +cell bool - +cell Does the word resemble an email address? + +cell Does the lexeme resemble an email address? +row +cell #[code is_oov] +cell bool - +cell Is the word out-of-vocabulary? + +cell Is the lexeme out-of-vocabulary? +row +cell #[code is_stop] +cell bool - +cell Is the word part of a "stop list"? + +cell Is the lexeme part of a "stop list"? +row +cell #[code lang] @@ -124,116 +293,9 @@ p An entry in the vocabulary. +row +cell #[code prob] +cell float - +cell Smoothed log probability estimate of token's type. + +cell Smoothed log probability estimate of lexeme's type. +row +cell #[code sentiment] +cell float - +cell A scalar value indicating the positivity or negativity of the token. - +row - +cell #[code lex_id] - +cell int - +cell ID of the token's lexical type. - - +row - +cell #[code text] - +cell unicode - +cell Verbatim text content. - -+h(2, "init") Lexeme.__init__ - +tag method - -p Create a #[code Lexeme] object. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code vocab] - +cell #[code Vocab] - +cell The parent vocabulary. - - +row - +cell #[code orth] - +cell int - +cell The orth id of the lexeme. - - +footrow - +cell return - +cell #[code Lexeme] - +cell The newly constructed object. - -+h(2, "set_flag") Lexeme.set_flag - +tag method - -p Change the value of a boolean flag. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code flag_id] - +cell int - +cell The attribute ID of the flag to set. - - +row - +cell #[code value] - +cell bool - +cell The new value of the flag. - - +footrow - +cell return - +cell #[code None] - +cell - - -+h(2, "check_flag") Lexeme.check_flag - +tag method - -p Check the value of a boolean flag. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code flag_id] - +cell int - +cell The attribute ID of the flag to query. - - +footrow - +cell return - +cell bool - +cell The value of the flag. - -+h(2, "similarity") Lexeme.similarity - +tag method - -p Compute a semantic similarity estimate. Defaults to cosine over vectors. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code other] - +cell - - +cell - | The object to compare with. By default, accepts #[code Doc], - | #[code Span], #[code Token] and #[code Lexeme] objects. - - +footrow - +cell return - +cell float - +cell A scalar similarity score. Higher is more similar. - -+h(2, "vector") Lexeme.vector - +tag property - -p A real-valued meaning representation. - -+table(["Name", "Type", "Description"]) - +footrow - +cell return - +cell #[code numpy.ndarray[ndim=1, dtype='float32']] - +cell A real-valued meaning representation. - -+h(2, "has_vector") Lexeme.has_vector - +tag property - -p A boolean value indicating whether a word vector is associated with the object. - -+table(["Name", "Type", "Description"]) - +footrow - +cell return - +cell bool - +cell Whether a word vector is associated with the object. + +cell A scalar value indicating the positivity or negativity of the lexeme. diff --git a/website/docs/api/matcher.jade b/website/docs/api/matcher.jade index 62bb4e33f..95819e553 100644 --- a/website/docs/api/matcher.jade +++ b/website/docs/api/matcher.jade @@ -4,31 +4,27 @@ include ../../_includes/_mixins p Match sequences of tokens, based on pattern rules. -+h(2, "load") Matcher.load - +tag classmethod - -p Load the matcher and patterns from a file path. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code path] - +cell #[code Path] - +cell Path to a JSON-formatted patterns file. - - +row - +cell #[code vocab] - +cell #[code Vocab] - +cell The vocabulary that the documents to match over will refer to. - - +footrow - +cell return - +cell #[code Matcher] - +cell The newly constructed object. ++infobox("⚠️ Deprecation note") + | As of spaCy 2.0, #[code Matcher.add_pattern] and #[code Matcher.add_entity] + | are deprecated and have been replaced with a simpler + | #[+api("matcher#add") #[code Matcher.add]] that lets you add a list of + | patterns and a callback for a given match ID. #[code Matcher.get_entity] + | is now called #[+api("matcher#get") #[code matcher.get]]. + | #[code Matcher.load] (not useful, as it didn't allow specifying callbacks), + | and #[code Matcher.has_entity] (now redundant) have been removed. The + | concept of "acceptor functions" has also been retired – this logic can + | now be handled in the callback functions. +h(2, "init") Matcher.__init__ +tag method -p Create the Matcher. +p Create the rule-based #[code Matcher]. + ++aside-code("Example"). + from spacy.matcher import Matcher + + patterns = {'HelloWorld': [{'LOWER': 'hello'}, {'LOWER': 'world'}]} + matcher = Matcher(nlp.vocab) +table(["Name", "Type", "Description"]) +row @@ -41,17 +37,26 @@ p Create the Matcher. +row +cell #[code patterns] +cell dict - +cell Patterns to add to the matcher. + +cell Patterns to add to the matcher, keyed by ID. +footrow - +cell return + +cell returns +cell #[code Matcher] +cell The newly constructed object. +h(2, "call") Matcher.__call__ +tag method -p Find all token sequences matching the supplied patterns on the Doc. +p Find all token sequences matching the supplied patterns on the #[code Doc]. + ++aside-code("Example"). + from spacy.matcher import Matcher + + matcher = Matcher(nlp.vocab) + pattern = [{'LOWER': "hello"}, {'LOWER': "world"}] + matcher.add("HelloWorld", None, pattern) + doc = nlp(u'hello world!') + matches = matcher(doc) +table(["Name", "Type", "Description"]) +row @@ -60,23 +65,39 @@ p Find all token sequences matching the supplied patterns on the Doc. +cell The document to match over. +footrow - +cell return + +cell returns +cell list +cell - | A list of#[code (entity_key, label_id, start, end)] tuples, - | describing the matches. A match tuple describes a - | #[code span doc[start:end]]. The #[code label_id] and - | #[code entity_key] are both integers. + | A list of #[code (match_id, start, end)] tuples, describing the + | matches. A match tuple describes a span #[code doc[start:end]]. + | The #[code match_id] is the ID of the added match pattern. + ++infobox("Important note") + | By default, the matcher #[strong does not perform any action] on matches, + | like tagging matched phrases with entity types. Instead, actions need to + | be specified when #[strong adding patterns or entities], by + | passing in a callback function as the #[code on_match] argument on + | #[+api("matcher#add") #[code add]]. This allows you to define custom + | actions per pattern within the same matcher. For example, you might only + | want to merge some entity types, and set custom flags for other matched + | patterns. For more details and examples, see the usage guide on + | #[+a("/docs/usage/rule-based-matching") rule-based matching]. +h(2, "pipe") Matcher.pipe +tag method p Match a stream of documents, yielding them in turn. ++aside-code("Example"). + from spacy.matcher import Matcher + matcher = Matcher(nlp.vocab) + for doc in matcher.pipe(texts, batch_size=50, n_threads=4): + pass + +table(["Name", "Type", "Description"]) +row +cell #[code docs] - +cell - + +cell iterable +cell A stream of documents. +row @@ -93,87 +114,150 @@ p Match a stream of documents, yielding them in turn. | multi-threading. +footrow - +cell yield + +cell yields +cell #[code Doc] +cell Documents, in order. -+h(2, "add_entity") Matcher.add_entity ++h(2, "len") Matcher.__len__ +tag method + +tag-new(2) -p Add an entity to the matcher. +p + | Get the number of rules added to the matcher. Note that this only returns + | the number of rules (identical with the number of IDs), not the number + | of individual patterns. + ++aside-code("Example"). + matcher = Matcher(nlp.vocab) + assert len(matcher) == 0 + matcher.add('Rule', None, [{'ORTH': 'test'}]) + assert len(matcher) == 1 + ++table(["Name", "Type", "Description"]) + +footrow + +cell returns + +cell int + +cell The number of rules. + ++h(2, "contains") Matcher.__contains__ + +tag method + +tag-new(2) + +p Check whether the matcher contains rules for a match ID. + ++aside-code("Example"). + matcher = Matcher(nlp.vocab) + assert 'Rule' not in matcher + matcher.add('Rule', None, [{'ORTH': 'test'}]) + assert 'Rule' in matcher +table(["Name", "Type", "Description"]) +row - +cell #[code entity_key] - +cell unicode / int - +cell An ID for the entity. - - +row - +cell #[code attrs] - +cell - - +cell Attributes to associate with the Matcher. - - +row - +cell #[code if_exists] + +cell #[code key] +cell unicode - +cell - | #[code 'raise'], #[code 'ignore'] or #[code 'update']. Controls - | what happens if the entity ID already exists. Defaults to - | #[code 'raise']. + +cell The match ID. + +footrow + +cell returns + +cell int + +cell Whether the matcher contains rules for this match ID. ++h(2, "add") Matcher.add + +tag method + +tag-new(2) + +p + | Add a rule to the matcher, consisting of an ID key, one or more patterns, and + | a callback function to act on the matches. The callback function will + | receive the arguments #[code matcher], #[code doc], #[code i] and + | #[code matches]. If a pattern already exists for the given ID, the + | patterns will be extended. An #[code on_match] callback will be + | overwritten. + ++aside-code("Example"). + def on_match(matcher, doc, id, matches): + print('Matched!', matches) + + matcher = Matcher(nlp.vocab) + matcher.add('HelloWorld', on_match, [{'LOWER': 'hello'}, {'LOWER': 'world'}]) + matcher.add('GoogleMaps', on_match, [{'ORTH': 'Google'}, {'ORTH': 'Maps'}]) + doc = nlp(u'HELLO WORLD on Google Maps.') + matches = matcher(doc) + ++table(["Name", "Type", "Description"]) +row - +cell #[code acceptor] - +cell - - +cell Callback function to filter matches of the entity. + +cell #[code match_id] + +cell unicode + +cell An ID for the thing you're matching. +row +cell #[code on_match] - +cell - - +cell Callback function to act on matches of the entity. + +cell callable or #[code None] + +cell + | Callback function to act on matches. Takes the arguments + | #[code matcher], #[code doc], #[code i] and #[code matches]. - +footrow - +cell return - +cell #[code None] - +cell - + +row + +cell #[code *patterns] + +cell list + +cell + | Match pattern. A pattern consists of a list of dicts, where each + | dict describes a token. -+h(2, "add_pattern") Matcher.add_pattern ++infobox("⚠️ Deprecation note") + .o-block + | As of spaCy 2.0, #[code Matcher.add_pattern] and #[code Matcher.add_entity] + | are deprecated and have been replaced with a simpler + | #[+api("matcher#add") #[code Matcher.add]] that lets you add a list of + | patterns and a callback for a given match ID. + + +code-new. + matcher.add('GoogleNow', merge_phrases, [{ORTH: 'Google'}, {ORTH: 'Now'}]) + + +code-old. + matcher.add_entity('GoogleNow', on_match=merge_phrases) + matcher.add_pattern('GoogleNow', [{ORTH: 'Google'}, {ORTH: 'Now'}]) + ++h(2, "remove") Matcher.remove +tag method + +tag-new(2) -p Add a pattern to the matcher. +p + | Remove a rule from the matcher. A #[code KeyError] is raised if the match + | ID does not exist. + ++aside-code("Example"). + matcher.add('Rule', None, [{'ORTH': 'test'}]) + assert 'Rule' in matcher + matcher.remove('Rule') + assert 'Rule' not in matcher +table(["Name", "Type", "Description"]) +row - +cell #[code entity_key] - +cell unicode / int - +cell An ID for the entity. + +cell #[code key] + +cell unicode + +cell The ID of the match rule. - +row - +cell #[code token_specs] - +cell - - +cell Description of the pattern to be matched. - - +row - +cell #[code label] - +cell unicode / int - +cell Label to assign to the matched pattern. Defaults to #[code ""]. - - +footrow - +cell return - +cell #[code None] - +cell - - -+h(2, "has_entity") Matcher.has_entity ++h(2, "get") Matcher.get +tag method + +tag-new(2) -p Check whether the matcher has an entity. +p + | Retrieve the pattern stored for a key. Returns the rule as an + | #[code (on_match, patterns)] tuple containing the callback and available + | patterns. + ++aside-code("Example"). + pattern = [{'ORTH': 'test'}] + matcher.add('Rule', None, pattern) + on_match, patterns = matcher.get('Rule') +table(["Name", "Type", "Description"]) +row - +cell #[code entity_key] - +cell unicode / int - +cell The entity key to check. + +cell #[code key] + +cell unicode + +cell The ID of the match rule. +footrow - +cell return - +cell bool - +cell Whether the matcher has the entity. + +cell returns + +cell tuple + +cell The rule, as an #[code (on_match, patterns)] tuple. diff --git a/website/docs/api/philosophy.jade b/website/docs/api/philosophy.jade deleted file mode 100644 index eda911045..000000000 --- a/website/docs/api/philosophy.jade +++ /dev/null @@ -1,14 +0,0 @@ -//- 💫 DOCS > API > PHILOSOPHY - -include ../../_includes/_mixins - -p Every product needs to know why it exists. Here's what we're trying to with spaCy and why it's different from other NLP libraries. - -+h(2) 1. No job too big. -p Most programs get cheaper to run over time, but NLP programs often get more expensive. The data often grows faster than the hardware improves. For web-scale tasks, Moore's law can't save us — so if we want to read the web, we have to sweat performance. - -+h(2) 2. Take a stand. -p Most NLP toolkits position themselves as platforms, rather than libraries. They offer a pluggable architecture, and leave it to the user to arrange the components they offer into a useful system. This is fine for researchers, but for production users, this does too little. Components go out of date quickly, and configuring a good system takes very detailed knowledge. Compatibility problems can be extremely subtle. spaCy is therefore extremely opinionated. The API does not expose any algorithmic details. You're free to configure another pipeline, but the core library eliminates redundancy, and only offers one choice of each component. - -+h(2) 3. Stay current. -p There's often significant improvement in NLP models year-on-year. This has been especially true recently, given the success of deep learning models. With spaCy, you should be able to build things you couldn't build yesterday. To deliver on that promise, we need to be giving you the latest stuff. diff --git a/website/docs/api/spacy.jade b/website/docs/api/spacy.jade new file mode 100644 index 000000000..a45307378 --- /dev/null +++ b/website/docs/api/spacy.jade @@ -0,0 +1,147 @@ +//- 💫 DOCS > API > SPACY + +include ../../_includes/_mixins + ++h(2, "load") spacy.load + +tag function + +tag-model + +p + | Load a model via its #[+a("/docs/usage/models#usage") shortcut link], + | the name of an installed + | #[+a("/docs/usage/saving-loading#generating") model package], a unicode + | path or a #[code Path]-like object. spaCy will try resolving the load + | argument in this order. If a model is loaded from a shortcut link or + | package name, spaCy will assume it's a Python package and import it and + | call the model's own #[code load()] method. If a model is loaded from a + | path, spaCy will assume it's a data directory, read the language and + | pipeline settings off the meta.json and initialise the #[code Language] + | class. The data will be loaded in via + | #[+api("language#from_disk") #[code Language.from_disk()]]. + ++aside-code("Example"). + nlp = spacy.load('en') # shortcut link + nlp = spacy.load('en_core_web_sm') # package + nlp = spacy.load('/path/to/en') # unicode path + nlp = spacy.load(Path('/path/to/en')) # pathlib Path + + nlp = spacy.load('en', disable=['parser', 'tagger']) + ++table(["Name", "Type", "Description"]) + +row + +cell #[code name] + +cell unicode or #[code Path] + +cell Model to load, i.e. shortcut link, package name or path. + + +row + +cell #[code disable] + +cell list + +cell + | Names of pipeline components to + | #[+a("/docs/usage/language-processing-pipeline#disabling") disable]. + + +footrow + +cell returns + +cell #[code Language] + +cell A #[code Language] object with the loaded model. + ++infobox("⚠️ Deprecation note") + .o-block + | As of spaCy 2.0, the #[code path] keyword argument is deprecated. spaCy + | will also raise an error if no model could be loaded and never just + | return an empty #[code Language] object. If you need a blank language, + | you need to import it explicitly (#[code from spacy.lang.en import English]) + | or use #[+api("util#get_lang_class") #[code util.get_lang_class]]. + + +code-new nlp = spacy.load('/model') + +code-old nlp = spacy.load('en', path='/model') + ++h(2, "info") spacy.info + +tag function + +p + | The same as the #[+api("cli#info") #[code info] command]. Pretty-print + | information about your installation, models and local setup from within + | spaCy. To get the model meta data as a dictionary instead, you can + | use the #[code meta] attribute on your #[code nlp] object with a + | loaded model, e.g. #[code nlp['meta']]. + ++aside-code("Example"). + spacy.info() + spacy.info('en') + spacy.info('de', markdown=True) + ++table(["Name", "Type", "Description"]) + +row + +cell #[code model] + +cell unicode + +cell A model, i.e. shortcut link, package name or path (optional). + + +row + +cell #[code markdown] + +cell bool + +cell Print information as Markdown. + + ++h(2, "explain") spacy.explain + +tag function + +p + | Get a description for a given POS tag, dependency label or entity type. + | For a list of available terms, see + | #[+src(gh("spacy", "spacy/glossary.py")) glossary.py]. + ++aside-code("Example"). + spacy.explain('NORP') + # Nationalities or religious or political groups + + doc = nlp(u'Hello world') + for word in doc: + print(word.text, word.tag_, spacy.explain(word.tag_)) + # Hello UH interjection + # world NN noun, singular or mass + ++table(["Name", "Type", "Description"]) + +row + +cell #[code term] + +cell unicode + +cell Term to explain. + + +footrow + +cell returns + +cell unicode + +cell The explanation, or #[code None] if not found in the glossary. + ++h(2, "set_factory") spacy.set_factory + +tag function + +tag-new(2) + +p + | Set a factory that returns a custom + | #[+a("/docs/usage/language-processing-pipeline") processing pipeline] + | component. Factories are useful for creating stateful components, especially ones which depend on shared data. + ++aside-code("Example"). + def my_factory(vocab): + def my_component(doc): + return doc + return my_component + + spacy.set_factory('my_factory', my_factory) + nlp = Language(pipeline=['my_factory']) + ++table(["Name", "Type", "Description"]) + +row + +cell #[code factory_id] + +cell unicode + +cell + | Unique name of factory. If added to a new pipeline, spaCy will + | look up the factory for this ID and use it to create the + | component. + + +row + +cell #[code factory] + +cell callable + +cell + | Callable that takes a #[code Vocab] object and returns a pipeline + | component. diff --git a/website/docs/api/span.jade b/website/docs/api/span.jade index 770ee3e9b..542336714 100644 --- a/website/docs/api/span.jade +++ b/website/docs/api/span.jade @@ -2,66 +2,18 @@ include ../../_includes/_mixins -p A slice from a #[code Doc] object. - -+h(2, "attributes") Attributes - -+table(["Name", "Type", "Description"]) - +row - +cell #[code doc] - +cell #[code Doc] - +cell The parent document. - - +row - +cell #[code start] - +cell int - +cell The token offset for the start of the span. - - +row - +cell #[code end] - +cell int - +cell The token offset for the end of the span. - - +row - +cell #[code start_char] - +cell int - +cell The character offset for the start of the span. - - +row - +cell #[code end_char] - +cell int - +cell The character offset for the end of the span. - - +row - +cell #[code label] - +cell int - +cell The span's label. - - +row - +cell #[code label_] - +cell unicode - +cell The span's label. - - +row - +cell #[code lemma_] - +cell unicode - +cell The span's lemma. - - +row - +cell #[code ent_id] - +cell int - +cell The integer ID of the named entity the token is an instance of. - - +row - +cell #[code ent_id_] - +cell unicode - +cell The string ID of the named entity the token is an instance of. +p A slice from a #[+api("doc") #[code Doc]] object. +h(2, "init") Span.__init__ +tag method p Create a Span object from the #[code slice doc[start : end]]. ++aside-code("Example"). + doc = nlp(u'Give it back! He pleaded.') + span = doc[1:4] + assert [t.text for t in span] == [u'it', u'back', u'!'] + +table(["Name", "Type", "Description"]) +row +cell #[code doc] @@ -89,7 +41,7 @@ p Create a Span object from the #[code slice doc[start : end]]. +cell A meaning representation of the span. +footrow - +cell return + +cell returns +cell #[code Span] +cell The newly constructed object. @@ -98,6 +50,11 @@ p Create a Span object from the #[code slice doc[start : end]]. p Get a #[code Token] object. ++aside-code("Example"). + doc = nlp(u'Give it back! He pleaded.') + span = doc[1:4] + assert span[1].text == 'back' + +table(["Name", "Type", "Description"]) +row +cell #[code i] @@ -105,12 +62,17 @@ p Get a #[code Token] object. +cell The index of the token within the span. +footrow - +cell return + +cell returns +cell #[code Token] +cell The token at #[code span[i]]. p Get a #[code Span] object. ++aside-code("Example"). + doc = nlp(u'Give it back! He pleaded.') + span = doc[1:4] + assert span[1:3].text == 'back!' + +table(["Name", "Type", "Description"]) +row +cell #[code start_end] @@ -118,7 +80,7 @@ p Get a #[code Span] object. +cell The slice of the span to get. +footrow - +cell return + +cell returns +cell #[code Span] +cell The span at #[code span[start : end]]. @@ -127,9 +89,14 @@ p Get a #[code Span] object. p Iterate over #[code Token] objects. ++aside-code("Example"). + doc = nlp(u'Give it back! He pleaded.') + span = doc[1:4] + assert [t.text for t in span] == ['it', 'back', '!'] + +table(["Name", "Type", "Description"]) +footrow - +cell yield + +cell yields +cell #[code Token] +cell A #[code Token] object. @@ -138,19 +105,33 @@ p Iterate over #[code Token] objects. p Get the number of tokens in the span. ++aside-code("Example"). + doc = nlp(u'Give it back! He pleaded.') + span = doc[1:4] + assert len(span) == 3 + +table(["Name", "Type", "Description"]) +footrow - +cell return + +cell returns +cell int +cell The number of tokens in the span. +h(2, "similarity") Span.similarity +tag method + +tag-model("vectors") p | Make a semantic similarity estimate. The default estimate is cosine | similarity using an average of word vectors. ++aside-code("Example"). + doc = nlp(u'green apples and red oranges') + green_apples = doc[:2] + red_oranges = doc[3:] + apples_oranges = green_apples.similarity(red_oranges) + oranges_apples = red_oranges.similarity(green_apples) + assert apples_oranges == oranges_apples + +table(["Name", "Type", "Description"]) +row +cell #[code other] @@ -160,7 +141,7 @@ p | #[code Span], #[code Token] and #[code Lexeme] objects. +footrow - +cell return + +cell returns +cell float +cell A scalar similarity score. Higher is more similar. @@ -178,87 +159,205 @@ p Retokenize the document, such that the span is merged into a single token. | are inherited from the syntactic root token of the span. +footrow - +cell return + +cell returns +cell #[code Token] +cell The newly merged token. -+h(2, "text") Span.text - +tag property - -p A unicode representation of the span text. - -+table(["Name", "Type", "Description"]) - +footrow - +cell return - +cell unicode - +cell The original verbatim text of the span. - -+h(2, "text_with_ws") Span.text_with_ws - +tag property - -p - | The text content of the span with a trailing whitespace character if the - | last token has one. - -+table(["Name", "Type", "Description"]) - +footrow - +cell return - +cell unicode - +cell The text content of the span (with trailing whitespace). - -+h(2, "sent") Span.sent - +tag property - -p The sentence span that this span is a part of. - -+table(["Name", "Type", "Description"]) - +footrow - +cell return - +cell #[code Span] - +cell The sentence this is part of. - +h(2, "root") Span.root +tag property + +tag-model("parse") p | The token within the span that's highest in the parse tree. If there's a | tie, the earlist is prefered. ++aside-code("Example"). + doc = nlp(u'I like New York in Autumn.') + i, like, new, york, in_, autumn, dot = range(len(doc)) + assert doc[new].head.text == 'York' + assert doc[york].head.text == 'like' + new_york = doc[new:york+1] + assert new_york.root.text == 'York' + +table(["Name", "Type", "Description"]) +footrow - +cell return + +cell returns +cell #[code Token] +cell The root token. +h(2, "lefts") Span.lefts +tag property + +tag-model("parse") p Tokens that are to the left of the span, whose head is within the span. ++aside-code("Example"). + doc = nlp(u'I like New York in Autumn.') + lefts = [t.text for t in doc[3:7].lefts] + assert lefts == [u'New'] + +table(["Name", "Type", "Description"]) +footrow - +cell yield + +cell yields +cell #[code Token] +cell A left-child of a token of the span. +h(2, "rights") Span.rights +tag property + +tag-model("parse") p Tokens that are to the right of the span, whose head is within the span. ++aside-code("Example"). + doc = nlp(u'I like New York in Autumn.') + rights = [t.text for t in doc[2:4].rights] + assert rights == [u'in'] + +table(["Name", "Type", "Description"]) +footrow - +cell yield + +cell yields +cell #[code Token] +cell A right-child of a token of the span. +h(2, "subtree") Span.subtree +tag property + +tag-model("parse") p Tokens that descend from tokens in the span, but fall outside it. ++aside-code("Example"). + doc = nlp(u'Give it back! He pleaded.') + subtree = [t.text for t in doc[:3].subtree] + assert subtree == [u'Give', u'it', u'back', u'!'] + +table(["Name", "Type", "Description"]) +footrow - +cell yield + +cell yields +cell #[code Token] +cell A descendant of a token within the span. + ++h(2, "has_vector") Span.has_vector + +tag property + +tag-model("vectors") + +p + | A boolean value indicating whether a word vector is associated with the + | object. + ++aside-code("Example"). + doc = nlp(u'I like apples') + assert doc[1:].has_vector + ++table(["Name", "Type", "Description"]) + +footrow + +cell returns + +cell bool + +cell Whether the span has a vector data attached. + ++h(2, "vector") Span.vector + +tag property + +tag-model("vectors") + +p + | A real-valued meaning representation. Defaults to an average of the + | token vectors. + ++aside-code("Example"). + doc = nlp(u'I like apples') + assert doc[1:].vector.dtype == 'float32' + assert doc[1:].vector.shape == (300,) + ++table(["Name", "Type", "Description"]) + +footrow + +cell returns + +cell #[code numpy.ndarray[ndim=1, dtype='float32']] + +cell A 1D numpy array representing the span's semantics. + ++h(2, "vector_norm") Span.vector_norm + +tag property + +tag-model("vectors") + +p + | The L2 norm of the span's vector representation. + ++aside-code("Example"). + doc = nlp(u'I like apples') + doc[1:].vector_norm # 4.800883928527915 + doc[2:].vector_norm # 6.895897646384268 + assert doc[1:].vector_norm != doc[2:].vector_norm + ++table(["Name", "Type", "Description"]) + +footrow + +cell returns + +cell float + +cell The L2 norm of the vector representation. + ++h(2, "attributes") Attributes + ++table(["Name", "Type", "Description"]) + +row + +cell #[code doc] + +cell #[code Doc] + +cell The parent document. + + +row + +cell #[code sent] + +cell #[code Span] + +cell The sentence span that this span is a part of. + + +row + +cell #[code start] + +cell int + +cell The token offset for the start of the span. + + +row + +cell #[code end] + +cell int + +cell The token offset for the end of the span. + + +row + +cell #[code start_char] + +cell int + +cell The character offset for the start of the span. + + +row + +cell #[code end_char] + +cell int + +cell The character offset for the end of the span. + + +row + +cell #[code text] + +cell unicode + +cell A unicode representation of the span text. + + +row + +cell #[code text_with_ws] + +cell unicode + +cell + | The text content of the span with a trailing whitespace character + | if the last token has one. + + +row + +cell #[code label] + +cell int + +cell The span's label. + + +row + +cell #[code label_] + +cell unicode + +cell The span's label. + + +row + +cell #[code lemma_] + +cell unicode + +cell The span's lemma. + + +row + +cell #[code ent_id] + +cell int + +cell The hash value of the named entity the token is an instance of. + + +row + +cell #[code ent_id_] + +cell unicode + +cell The string ID of the named entity the token is an instance of. diff --git a/website/docs/api/stringstore.jade b/website/docs/api/stringstore.jade index 3cd62cc1e..c17fb1db9 100644 --- a/website/docs/api/stringstore.jade +++ b/website/docs/api/stringstore.jade @@ -2,21 +2,29 @@ include ../../_includes/_mixins -p Map strings to and from integer IDs. +p + | Look up strings by 64-bit hashes. As of v2.0, spaCy uses hash values + | instead of integer IDs. This ensures that strings always map to the + | same ID, even from different #[code StringStores]. +h(2, "init") StringStore.__init__ +tag method -p Create the #[code StringStore]. +p + | Create the #[code StringStore]. + ++aside-code("Example"). + from spacy.strings import StringStore + stringstore = StringStore([u'apple', u'orange']) +table(["Name", "Type", "Description"]) +row +cell #[code strings] - +cell - + +cell iterable +cell A sequence of unicode strings to add to the store. +footrow - +cell return + +cell returns +cell #[code StringStore] +cell The newly constructed object. @@ -25,33 +33,48 @@ p Create the #[code StringStore]. p Get the number of strings in the store. ++aside-code("Example"). + stringstore = StringStore([u'apple', u'orange']) + assert len(stringstore) == 2 + +table(["Name", "Type", "Description"]) +footrow - +cell return + +cell returns +cell int +cell The number of strings in the store. +h(2, "getitem") StringStore.__getitem__ +tag method -p Retrieve a string from a given integer ID, or vice versa. +p Retrieve a string from a given hash, or vice versa. + ++aside-code("Example"). + stringstore = StringStore([u'apple', u'orange']) + apple_hash = stringstore[u'apple'] + assert apple_hash == 8566208034543834098 + assert stringstore[apple_hash] == u'apple' +table(["Name", "Type", "Description"]) +row +cell #[code string_or_id] - +cell bytes / unicode / int + +cell bytes, unicode or uint64 +cell The value to encode. +footrow - +cell return - +cell unicode / int - +cell The value to retrieved. + +cell returns + +cell unicode or int + +cell The value to be retrieved. +h(2, "contains") StringStore.__contains__ +tag method p Check whether a string is in the store. ++aside-code("Example"). + stringstore = StringStore([u'apple', u'orange']) + assert u'apple' in stringstore + assert not u'cherry' in stringstore + +table(["Name", "Type", "Description"]) +row +cell #[code string] @@ -59,49 +82,158 @@ p Check whether a string is in the store. +cell The string to check. +footrow - +cell return + +cell returns +cell bool +cell Whether the store contains the string. +h(2, "iter") StringStore.__iter__ +tag method -p Iterate over the strings in the store, in order. +p + | Iterate over the strings in the store, in order. Note that a newly + | initialised store will always include an empty string #[code ''] at + | position #[code 0]. + ++aside-code("Example"). + stringstore = StringStore([u'apple', u'orange']) + all_strings = [s for s in stringstore] + assert all_strings == [u'apple', u'orange'] +table(["Name", "Type", "Description"]) +footrow - +cell yield + +cell yields +cell unicode +cell A string in the store. -+h(2, "dump") StringStore.dump ++h(2, "add") StringStore.add +tag method + +tag-new(2) -p Save the strings to a JSON file. +p Add a string to the #[code StringStore]. + ++aside-code("Example"). + stringstore = StringStore([u'apple', u'orange']) + banana_hash = stringstore.add(u'banana') + assert len(stringstore) == 3 + assert banana_hash == 2525716904149915114 + assert stringstore[banana_hash] == u'banana' + assert stringstore[u'banana'] == banana_hash +table(["Name", "Type", "Description"]) +row - +cell #[code file] - +cell buffer - +cell The file to save the strings. + +cell #[code string] + +cell unicode + +cell The string to add. +footrow - +cell return - +cell #[code None] - +cell - + +cell returns + +cell uint64 + +cell The string's hash value. -+h(2, "load") StringStore.load + ++h(2, "to_disk") StringStore.to_disk +tag method + +tag-new(2) -p Load the strings from a JSON file. +p Save the current state to a directory. + ++aside-code("Example"). + stringstore.to_disk('/path/to/strings') +table(["Name", "Type", "Description"]) +row - +cell #[code file] - +cell buffer - +cell The file from which to load the strings. + +cell #[code path] + +cell unicode or #[code Path] + +cell + | A path to a directory, which will be created if it doesn't exist. + | Paths may be either strings or #[code Path]-like objects. + ++h(2, "from_disk") StringStore.from_disk + +tag method + +tag-new(2) + +p Loads state from a directory. Modifies the object in place and returns it. + ++aside-code("Example"). + from spacy.strings import StringStore + stringstore = StringStore().from_disk('/path/to/strings') + ++table(["Name", "Type", "Description"]) + +row + +cell #[code path] + +cell unicode or #[code Path] + +cell + | A path to a directory. Paths may be either strings or + | #[code Path]-like objects. +footrow - +cell return - +cell #[code None] + +cell returns + +cell #[code StringStore] + +cell The modified #[code StringStore] object. + ++h(2, "to_bytes") StringStore.to_bytes + +tag method + +p Serialize the current state to a binary string. + ++aside-code("Example"). + store_bytes = stringstore.to_bytes() + ++table(["Name", "Type", "Description"]) + +row + +cell #[code **exclude] +cell - + +cell Named attributes to prevent from being serialized. + + +footrow + +cell returns + +cell bytes + +cell The serialized form of the #[code StringStore] object. + ++h(2, "from_bytes") StringStore.from_bytes + +tag method + +p Load state from a binary string. + ++aside-code("Example"). + fron spacy.strings import StringStore + store_bytes = stringstore.to_bytes() + new_store = StringStore().from_bytes(store_bytes) + ++table(["Name", "Type", "Description"]) + +row + +cell #[code bytes_data] + +cell bytes + +cell The data to load from. + + +row + +cell #[code **exclude] + +cell - + +cell Named attributes to prevent from being loaded. + + +footrow + +cell returns + +cell #[code StringStore] + +cell The #[code StringStore] object. + ++h(2, "util") Utilities + ++h(3, "hash_string") strings.hash_string + +tag function + +p Get a 64-bit hash for a given string. + ++aside-code("Example"). + from spacy.strings import hash_string + assert hash_string(u'apple') == 8566208034543834098 + ++table(["Name", "Type", "Description"]) + +row + +cell #[code string] + +cell unicode + +cell The string to hash. + + +footrow + +cell returns + +cell uint64 + +cell The hash. diff --git a/website/docs/api/tagger.jade b/website/docs/api/tagger.jade index 77c696108..c41de6a4e 100644 --- a/website/docs/api/tagger.jade +++ b/website/docs/api/tagger.jade @@ -4,31 +4,7 @@ include ../../_includes/_mixins p Annotate part-of-speech tags on #[code Doc] objects. -+h(2, "load") Tagger.load - +tag classmethod - -p Load the statistical model from the supplied path. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code path] - +cell #[code Path] - +cell The path to load from. - - +row - +cell #[code vocab] - +cell #[code Vocab] - +cell The vocabulary. Must be shared by the documents to be processed. - - +row - +cell #[code require] - +cell bool - +cell Whether to raise an error if the files are not found. - - +footrow - +cell return - +cell #[code Tagger] - +cell The newly constructed object. ++under-construction +h(2, "init") Tagger.__init__ +tag method @@ -47,7 +23,7 @@ p Create a #[code Tagger]. +cell The statistical model. +footrow - +cell return + +cell returns +cell #[code Tagger] +cell The newly constructed object. @@ -63,7 +39,7 @@ p Apply the tagger, setting the POS tags onto the #[code Doc] object. +cell The tokens to be tagged. +footrow - +cell return + +cell returns +cell #[code None] +cell - @@ -91,7 +67,7 @@ p Tag a stream of documents. | parallel. +footrow - +cell yield + +cell yields +cell #[code Doc] +cell Documents, in order. @@ -112,6 +88,6 @@ p Update the statistical model, with tags supplied for the given document. +cell Manager for the gold-standard tags. +footrow - +cell return + +cell returns +cell int +cell Number of tags predicted correctly. diff --git a/website/docs/api/tensorizer.jade b/website/docs/api/tensorizer.jade new file mode 100644 index 000000000..9abd6793b --- /dev/null +++ b/website/docs/api/tensorizer.jade @@ -0,0 +1,7 @@ +//- 💫 DOCS > API > TENSORIZER + +include ../../_includes/_mixins + +p Add a tensor with position-sensitive meaning representations to a #[code Doc]. + ++under-construction diff --git a/website/docs/api/textcategorizer.jade b/website/docs/api/textcategorizer.jade new file mode 100644 index 000000000..926d957f7 --- /dev/null +++ b/website/docs/api/textcategorizer.jade @@ -0,0 +1,21 @@ +//- 💫 DOCS > API > TEXTCATEGORIZER + +include ../../_includes/_mixins + +p + | Add text categorization models to spaCy pipelines. The model supports + | classification with multiple, non-mutually exclusive labels. + +p + | You can change the model architecture rather easily, but by default, the + | #[code TextCategorizer] class uses a convolutional neural network to + | assign position-sensitive vectors to each word in the document. This step + | is similar to the #[+api("tensorizer") #[code Tensorizer]] component, but the + | #[code TextCategorizer] uses its own CNN model, to avoid sharing weights + | with the other pipeline components. The document tensor is then + | summarized by concatenating max and mean pooling, and a multilayer + | perceptron is used to predict an output vector of length #[code nr_class], + | before a logistic activation is applied elementwise. The value of each + | output neuron is the probability that some class is present. + ++under-construction diff --git a/website/docs/api/token.jade b/website/docs/api/token.jade index 7a09f9d11..87387e09d 100644 --- a/website/docs/api/token.jade +++ b/website/docs/api/token.jade @@ -4,9 +4,296 @@ include ../../_includes/_mixins p An individual token — i.e. a word, punctuation symbol, whitespace, etc. ++h(2, "init") Token.__init__ + +tag method + +p Construct a #[code Token] object. + ++aside-code("Example"). + doc = nlp(u'Give it back! He pleaded.') + token = doc[0] + assert token.text == u'Give' + ++table(["Name", "Type", "Description"]) + +row + +cell #[code vocab] + +cell #[code Vocab] + +cell A storage container for lexical types. + + +row + +cell #[code doc] + +cell #[code Doc] + +cell The parent document. + + +row + +cell #[code offset] + +cell int + +cell The index of the token within the document. + + +footrow + +cell returns + +cell #[code Token] + +cell The newly constructed object. + ++h(2, "len") Token.__len__ + +tag method + +p The number of unicode characters in the token, i.e. #[code token.text]. + ++aside-code("Example"). + doc = nlp(u'Give it back! He pleaded.') + token = doc[0] + assert len(token) == 4 + ++table(["Name", "Type", "Description"]) + +footrow + +cell returns + +cell int + +cell The number of unicode characters in the token. + ++h(2, "check_flag") Token.check_flag + +tag method + +p Check the value of a boolean flag. + ++aside-code("Example"). + from spacy.attrs import IS_TITLE + doc = nlp(u'Give it back! He pleaded.') + token = doc[0] + assert token.check_flag(IS_TITLE) == True + ++table(["Name", "Type", "Description"]) + +row + +cell #[code flag_id] + +cell int + +cell The attribute ID of the flag to check. + + +footrow + +cell returns + +cell bool + +cell Whether the flag is set. + ++h(2, "similarity") Token.similarity + +tag method + +tag-model("vectors") + +p Compute a semantic similarity estimate. Defaults to cosine over vectors. + ++aside-code("Example"). + apples, _, oranges = nlp(u'apples and oranges') + apples_oranges = apples.similarity(oranges) + oranges_apples = oranges.similarity(apples) + assert apples_oranges == oranges_apples + ++table(["Name", "Type", "Description"]) + +row + +cell other + +cell - + +cell + | The object to compare with. By default, accepts #[code Doc], + | #[code Span], #[code Token] and #[code Lexeme] objects. + + +footrow + +cell returns + +cell float + +cell A scalar similarity score. Higher is more similar. + ++h(2, "nbor") Token.nbor + +tag method + +p Get a neighboring token. + ++aside-code("Example"). + doc = nlp(u'Give it back! He pleaded.') + give_nbor = doc[0].nbor() + assert give_nbor.text == u'it' + ++table(["Name", "Type", "Description"]) + +row + +cell #[code i] + +cell int + +cell The relative position of the token to get. Defaults to #[code 1]. + + +footrow + +cell returns + +cell #[code Token] + +cell The token at position #[code self.doc[self.i+i]]. + ++h(2, "is_ancestor") Token.is_ancestor + +tag method + +tag-model("parse") + +p + | Check whether this token is a parent, grandparent, etc. of another + | in the dependency tree. + ++aside-code("Example"). + doc = nlp(u'Give it back! He pleaded.') + give = doc[0] + it = doc[1] + assert give.is_ancestor(it) + ++table(["Name", "Type", "Description"]) + +row + +cell descendant + +cell #[code Token] + +cell Another token. + + +footrow + +cell returns + +cell bool + +cell Whether this token is the ancestor of the descendant. + ++h(2, "ancestors") Token.ancestors + +tag property + +tag-model("parse") + +p The rightmost token of this token's syntactic descendants. + ++aside-code("Example"). + doc = nlp(u'Give it back! He pleaded.') + it_ancestors = doc[1].ancestors + assert [t.text for t in it_ancestors] == [u'Give'] + he_ancestors = doc[4].ancestors + assert [t.text for t in he_ancestors] == [u'pleaded'] + ++table(["Name", "Type", "Description"]) + +footrow + +cell yields + +cell #[code Token] + +cell + | A sequence of ancestor tokens such that + | #[code ancestor.is_ancestor(self)]. + ++h(2, "conjuncts") Token.conjuncts + +tag property + +tag-model("parse") + +p A sequence of coordinated tokens, including the token itself. + ++aside-code("Example"). + doc = nlp(u'I like apples and oranges') + apples_conjuncts = doc[2].conjuncts + assert [t.text for t in apples_conjuncts] == [u'oranges'] + ++table(["Name", "Type", "Description"]) + +footrow + +cell yields + +cell #[code Token] + +cell A coordinated token. + ++h(2, "children") Token.children + +tag property + +tag-model("parse") + +p A sequence of the token's immediate syntactic children. + ++aside-code("Example"). + doc = nlp(u'Give it back! He pleaded.') + give_children = doc[0].children + assert [t.text for t in give_children] == [u'it', u'back', u'!'] + ++table(["Name", "Type", "Description"]) + +footrow + +cell yields + +cell #[code Token] + +cell A child token such that #[code child.head==self]. + ++h(2, "subtree") Token.subtree + +tag property + +tag-model("parse") + +p A sequence of all the token's syntactic descendents. + ++aside-code("Example"). + doc = nlp(u'Give it back! He pleaded.') + give_subtree = doc[0].subtree + assert [t.text for t in give_subtree] == [u'Give', u'it', u'back', u'!'] + ++table(["Name", "Type", "Description"]) + +footrow + +cell yields + +cell #[code Token] + +cell A descendant token such that #[code self.is_ancestor(descendant)]. + ++h(2, "has_vector") Token.has_vector + +tag property + +tag-model("vectors") + +p + | A boolean value indicating whether a word vector is associated with the + | token. + ++aside-code("Example"). + doc = nlp(u'I like apples') + apples = doc[2] + assert apples.has_vector + ++table(["Name", "Type", "Description"]) + +footrow + +cell returns + +cell bool + +cell Whether the token has a vector data attached. + ++h(2, "vector") Token.vector + +tag property + +tag-model("vectors") + +p A real-valued meaning representation. + ++aside-code("Example"). + doc = nlp(u'I like apples') + apples = doc[2] + assert apples.vector.dtype == 'float32' + assert apples.vector.shape == (300,) + ++table(["Name", "Type", "Description"]) + +footrow + +cell returns + +cell #[code numpy.ndarray[ndim=1, dtype='float32']] + +cell A 1D numpy array representing the token's semantics. + ++h(2, "vector_norm") Span.vector_norm + +tag property + +tag-model("vectors") + +p The L2 norm of the token's vector representation. + ++aside-code("Example"). + doc = nlp(u'I like apples and pasta') + apples = doc[2] + pasta = doc[4] + apples.vector_norm # 6.89589786529541 + pasta.vector_norm # 7.759851932525635 + assert apples.vector_norm != pasta.vector_norm + ++table(["Name", "Type", "Description"]) + +footrow + +cell returns + +cell float + +cell The L2 norm of the vector representation. + +h(2, "attributes") Attributes +table(["Name", "Type", "Description"]) + +row + +cell #[code text] + +cell unicode + +cell Verbatim text content. + +row + +cell #[code text_with_ws] + +cell unicode + +cell Text content, with trailing space character if present. + + +row + +cell #[code whitespace] + +cell int + +cell Trailing space character if present. + +row + +cell #[code whitespace_] + +cell unicode + +cell Trailing space character if present. + +row +cell #[code vocab] +cell #[code Vocab] @@ -17,14 +304,31 @@ p An individual token — i.e. a word, punctuation symbol, whitespace, etc. +cell #[code Doc] +cell The parent document. + +row + +cell #[code head] + +cell #[code Token] + +cell The syntactic parent, or "governor", of this token. + + +row + +cell #[code left_edge] + +cell #[code Token] + +cell The leftmost token of this token's syntactic descendants. + + +row + +cell #[code right_edge] + +cell #[code Token] + +cell The rightmost token of this token's syntactic descendents. + +row +cell #[code i] +cell int +cell The index of the token within the parent document. + +row +cell #[code ent_type] +cell int +cell Named entity type. + +row +cell #[code ent_type_] +cell unicode @@ -34,142 +338,166 @@ p An individual token — i.e. a word, punctuation symbol, whitespace, etc. +cell #[code ent_iob] +cell int +cell - | IOB code of named entity tag. - | #[code 1="I", 2="O", 3="B"]. #[code 0] means no tag is assigned. + | IOB code of named entity tag. #[code "B"] + | means the token begins an entity, #[code "I"] means it is inside + | an entity, #[code "O"] means it is outside an entity, and + | #[code ""] means no entity tag is set. +row +cell #[code ent_iob_] +cell unicode +cell | IOB code of named entity tag. #[code "B"] - | means the token begins an entity, #[code "I"] means it inside an - | entity, #[code "O"] means it is outside an entity, and + | means the token begins an entity, #[code "I"] means it is inside + | an entity, #[code "O"] means it is outside an entity, and | #[code ""] means no entity tag is set. +row +cell #[code ent_id] +cell int - +cell ID of the entity the token is an instance of, if any. + +cell + | ID of the entity the token is an instance of, if any. Usually + | assigned by patterns in the Matcher. +row +cell #[code ent_id_] +cell unicode - +cell ID of the entity the token is an instance of, if any. + +cell + | ID of the entity the token is an instance of, if any. Usually + | assigned by patterns in the Matcher. +row +cell #[code lemma] +cell int +cell - | Base form of the word, with no inflectional suffixes. + | Base form of the token, with no inflectional suffixes. +row +cell #[code lemma_] +cell unicode - +cell Base form of the word, with no inflectional suffixes. + +cell Base form of the token, with no inflectional suffixes. +row +cell #[code lower] +cell int - +cell Lower-case form of the word. + +cell Lower-case form of the token. +row +cell #[code lower_] +cell unicode - +cell Lower-case form of the word. + +cell Lower-case form of the token. +row +cell #[code shape] +cell int - +cell Transform of the word's string, to show orthographic features. + +cell + | Transform of the tokens's string, to show orthographic features. + | For example, "Xxxx" or "dd". +row +cell #[code shape_] +cell unicode - +cell A transform of the word's string, to show orthographic features. + +cell + | Transform of the tokens's string, to show orthographic features. + | For example, "Xxxx" or "dd". +row +cell #[code prefix] +cell int - +cell Integer ID of a length-N substring from the start of the - | word. Defaults to #[code N=1]. + +cell + | Hash value of a length-N substring from the start of the + | token. Defaults to #[code N=1]. +row +cell #[code prefix_] +cell unicode +cell - | A length-N substring from the start of the word. Defaults to + | A length-N substring from the start of the token. Defaults to | #[code N=1]. +row +cell #[code suffix] +cell int +cell - | Length-N substring from the end of the word. Defaults to #[code N=3]. + | Hash value of a length-N substring from the end of the token. + | Defaults to #[code N=3]. +row +cell #[code suffix_] +cell unicode - +cell Length-N substring from the end of the word. Defaults to #[code N=3]. + +cell Length-N substring from the end of the token. Defaults to #[code N=3]. +row +cell #[code is_alpha] +cell bool - +cell Equivalent to #[code word.orth_.isalpha()]. + +cell + | Does the token consist of alphabetic characters? Equivalent to + | #[code token.text.isalpha()]. +row +cell #[code is_ascii] +cell bool - +cell Equivalent to #[code [any(ord(c) >= 128 for c in word.orth_)]]. + +cell + | Does the token consist of ASCII characters? Equivalent to + | #[code [any(ord(c) >= 128 for c in token.text)]]. +row +cell #[code is_digit] +cell bool - +cell Equivalent to #[code word.orth_.isdigit()]. + +cell + | Does the token consist of digits? Equivalent to + | #[code token.text.isdigit()]. +row +cell #[code is_lower] +cell bool - +cell Equivalent to #[code word.orth_.islower()]. + +cell + | Is the token in lowercase? Equivalent to + | #[code token.text.islower()]. +row +cell #[code is_title] +cell bool - +cell Equivalent to #[code word.orth_.istitle()]. + +cell + | Is the token in titlecase? Equivalent to + | #[code token.text.istitle()]. +row +cell #[code is_punct] +cell bool - +cell Equivalent to #[code word.orth_.ispunct()]. + +cell Is the token punctuation? +row +cell #[code is_space] +cell bool - +cell Equivalent to #[code word.orth_.isspace()]. + +cell + | Does the token consist of whitespace characters? Equivalent to + | #[code token.text.isspace()]. +row +cell #[code like_url] +cell bool - +cell Does the word resemble a URL? + +cell Does the token resemble a URL? +row +cell #[code like_num] +cell bool - +cell Does the word represent a number? e.g. “10.9”, “10”, “ten”, etc. + +cell Does the token represent a number? e.g. "10.9", "10", "ten", etc. +row +cell #[code like_email] +cell bool - +cell Does the word resemble an email address? + +cell Does the token resemble an email address? +row +cell #[code is_oov] +cell bool - +cell Is the word out-of-vocabulary? + +cell Is the token out-of-vocabulary? +row +cell #[code is_stop] +cell bool - +cell Is the word part of a "stop list"? + +cell Is the token part of a "stop list"? +row +cell #[code pos] @@ -229,232 +557,3 @@ p An individual token — i.e. a word, punctuation symbol, whitespace, etc. +cell #[code lex_id] +cell int +cell ID of the token's lexical type. - - +row - +cell #[code text] - +cell unicode - +cell Verbatim text content. - +row - +cell #[code text_with_ws] - +cell unicode - +cell Text content, with trailing space character if present. - - +row - +cell #[code whitespace] - +cell int - +cell Trailing space character if present. - +row - +cell #[code whitespace_] - +cell unicode - +cell Trailing space character if present. - - -+h(2, "init") Token.__init__ - +tag method - -p Construct a #[code Token] object. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code vocab] - +cell #[code Vocab] - +cell A storage container for lexical types. - - +row - +cell #[code doc] - +cell #[code Doc] - +cell The parent document. - - +row - +cell #[code offset] - +cell int - +cell The index of the token within the document. - - +footrow - +cell return - +cell #[code Token] - +cell The newly constructed object. - -+h(2, "len") Token.__len__ - +tag method - -p Get the number of unicode characters in the token. - -+table(["Name", "Type", "Description"]) - +footrow - +cell return - +cell int - +cell The number of unicode characters in the token. - - -+h(2, "check_flag") Token.check_flag - +tag method - -p Check the value of a boolean flag. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code flag_id] - +cell int - +cell The attribute ID of the flag to check. - - +footrow - +cell return - +cell bool - +cell Whether the flag is set. - -+h(2, "nbor") Token.nbor - +tag method - -p Get a neighboring token. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code i] - +cell int - +cell The relative position of the token to get. Defaults to #[code 1]. - - +footrow - +cell return - +cell #[code Token] - +cell The token at position #[code self.doc[self.i+i]] - -+h(2, "similarity") Token.similarity - +tag method - -p Compute a semantic similarity estimate. Defaults to cosine over vectors. - -+table(["Name", "Type", "Description"]) - +row - +cell other - +cell - - +cell - | The object to compare with. By default, accepts #[code Doc], - | #[code Span], #[code Token] and #[code Lexeme] objects. - - +footrow - +cell return - +cell float - +cell A scalar similarity score. Higher is more similar. - -+h(2, "is_ancestor") Token.is_ancestor - +tag method - -p - | Check whether this token is a parent, grandparent, etc. of another - | in the dependency tree. - -+table(["Name", "Type", "Description"]) - +row - +cell descendant - +cell #[code Token] - +cell Another token. - - +footrow - +cell return - +cell bool - +cell Whether this token is the ancestor of the descendant. - - -+h(2, "vector") Token.vector - +tag property - -p A real-valued meaning representation. - -+table(["Name", "Type", "Description"]) - +footrow - +cell return - +cell #[code numpy.ndarray[ndim=1, dtype='float32']] - +cell A 1D numpy array representing the token's semantics. - -+h(2, "has_vector") Token.has_vector - +tag property - -p - | A boolean value indicating whether a word vector is associated with the - | object. - -+table(["Name", "Type", "Description"]) - +footrow - +cell return - +cell bool - +cell Whether the token has a vector data attached. - -+h(2, "head") Token.head - +tag property - -p The syntactic parent, or "governor", of this token. - -+table(["Name", "Type", "Description"]) - +footrow - +cell return - +cell #[code Token] - +cell The head. - -+h(2, "conjuncts") Token.conjuncts - +tag property - -p A sequence of coordinated tokens, including the token itself. - -+table(["Name", "Type", "Description"]) - +footrow - +cell yield - +cell #[code Token] - +cell A coordinated token. - -+h(2, "children") Token.children - +tag property - -p A sequence of the token's immediate syntactic children. - -+table(["Name", "Type", "Description"]) - +footrow - +cell yield - +cell #[code Token] - +cell A child token such that #[code child.head==self]. - -+h(2, "subtree") Token.subtree - +tag property - -p A sequence of all the token's syntactic descendents. - -+table(["Name", "Type", "Description"]) - +footrow - +cell yield - +cell #[code Token] - +cell A descendant token such that #[code self.is_ancestor(descendant)]. - -+h(2, "left_edge") Token.left_edge - +tag property - -p The leftmost token of this token's syntactic descendants. - -+table(["Name", "Type", "Description"]) - +footrow - +cell return - +cell #[code Token] - +cell The first token such that #[code self.is_ancestor(token)]. - -+h(2, "right_edge") Token.right_edge - +tag property - -p The rightmost token of this token's syntactic descendents. - -+table(["Name", "Type", "Description"]) - +footrow - +cell return - +cell #[code Token] - +cell The last token such that #[code self.is_ancestor(token)]. - -+h(2, "ancestors") Token.ancestors - +tag property - -p The rightmost token of this token's syntactic descendants. - -+table(["Name", "Type", "Description"]) - +footrow - +cell yield - +cell #[code Token] - +cell - | A sequence of ancestor tokens such that - | #[code ancestor.is_ancestor(self)]. diff --git a/website/docs/api/tokenizer.jade b/website/docs/api/tokenizer.jade index 44ba0fc69..196f886b7 100644 --- a/website/docs/api/tokenizer.jade +++ b/website/docs/api/tokenizer.jade @@ -6,6 +6,198 @@ p | Segment text, and create #[code Doc] objects with the discovered segment | boundaries. ++h(2, "init") Tokenizer.__init__ + +tag method + +p Create a #[code Tokenizer], to create #[code Doc] objects given unicode text. + ++aside-code("Example"). + # Construction 1 + from spacy.tokenizer import Tokenizer + tokenizer = Tokenizer(nlp.vocab) + + # Construction 2 + from spacy.lang.en import English + tokenizer = English().Defaults.create_tokenizer(nlp) + ++table(["Name", "Type", "Description"]) + +row + +cell #[code vocab] + +cell #[code Vocab] + +cell A storage container for lexical types. + + +row + +cell #[code rules] + +cell dict + +cell Exceptions and special-cases for the tokenizer. + + +row + +cell #[code prefix_search] + +cell callable + +cell + | A function matching the signature of + | #[code re.compile(string).search] to match prefixes. + + +row + +cell #[code suffix_search] + +cell callable + +cell + | A function matching the signature of + | #[code re.compile(string).search] to match suffixes. + + +row + +cell #[code infix_finditer] + +cell callable + +cell + | A function matching the signature of + | #[code re.compile(string).finditer] to find infixes. + + +row + +cell #[code token_match] + +cell callable + +cell A boolean function matching strings to be recognised as tokens. + + +footrow + +cell returns + +cell #[code Tokenizer] + +cell The newly constructed object. + ++h(2, "call") Tokenizer.__call__ + +tag method + +p Tokenize a string. + ++aside-code("Example"). + tokens = tokenizer(u'This is a sentence') + assert len(tokens) == 4 + ++table(["Name", "Type", "Description"]) + +row + +cell #[code string] + +cell unicode + +cell The string to tokenize. + + +footrow + +cell returns + +cell #[code Doc] + +cell A container for linguistic annotations. + ++h(2, "pipe") Tokenizer.pipe + +tag method + +p Tokenize a stream of texts. + ++aside-code("Example"). + texts = [u'One document.', u'...', u'Lots of documents'] + for doc in tokenizer.pipe(texts, batch_size=50): + pass + ++table(["Name", "Type", "Description"]) + +row + +cell #[code texts] + +cell - + +cell A sequence of unicode texts. + + +row + +cell #[code batch_size] + +cell int + +cell The number of texts to accumulate in an internal buffer. + + +row + +cell #[code n_threads] + +cell int + +cell + | The number of threads to use, if the implementation supports + | multi-threading. The default tokenizer is single-threaded. + + +footrow + +cell yields + +cell #[code Doc] + +cell A sequence of Doc objects, in order. + ++h(2, "find_infix") Tokenizer.find_infix + +tag method + +p Find internal split points of the string. + ++table(["Name", "Type", "Description"]) + +row + +cell #[code string] + +cell unicode + +cell The string to split. + + +footrow + +cell returns + +cell list + +cell + | A list of #[code re.MatchObject] objects that have #[code .start()] + | and #[code .end()] methods, denoting the placement of internal + | segment separators, e.g. hyphens. + ++h(2, "find_prefix") Tokenizer.find_prefix + +tag method + +p + | Find the length of a prefix that should be segmented from the string, or + | #[code None] if no prefix rules match. + ++table(["Name", "Type", "Description"]) + +row + +cell #[code string] + +cell unicode + +cell The string to segment. + + +footrow + +cell returns + +cell int + +cell The length of the prefix if present, otherwise #[code None]. + ++h(2, "find_suffix") Tokenizer.find_suffix + +tag method + +p + | Find the length of a suffix that should be segmented from the string, or + | #[code None] if no suffix rules match. + ++table(["Name", "Type", "Description"]) + +row + +cell #[code string] + +cell unicode + +cell The string to segment. + + +footrow + +cell returns + +cell int / #[code None] + +cell The length of the suffix if present, otherwise #[code None]. + ++h(2, "add_special_case") Tokenizer.add_special_case + +tag method + +p + | Add a special-case tokenization rule. This mechanism is also used to add + | custom tokenizer exceptions to the language data. See the usage guide + | on #[+a("/docs/usage/adding-languages#tokenizer-exceptions") adding languages] + | for more details and examples. + ++aside-code("Example"). + from spacy.attrs import ORTH, LEMMA + case = [{"don't": [{ORTH: "do"}, {ORTH: "n't", LEMMA: "not"}]}] + tokenizer.add_special_case(case) + ++table(["Name", "Type", "Description"]) + +row + +cell #[code string] + +cell unicode + +cell The string to specially tokenize. + + +row + +cell #[code token_attrs] + +cell iterable + +cell + | A sequence of dicts, where each dict describes a token and its + | attributes. The #[code ORTH] fields of the attributes must + | exactly match the string when they are concatenated. + +h(2, "attributes") Attributes +table(["Name", "Type", "Description"]) @@ -35,215 +227,3 @@ p | A function to find internal segment separators, e.g. hyphens. | Returns a (possibly empty) list of #[code re.MatchObject] | objects. - -+h(2, "load") Tokenizer.load - +tag classmethod - -p Load a #[code Tokenizer], reading unsupplied components from the path. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code path] - +cell #[code Path] - +cell The path to load from. - - +row - +cell #[code vocab] - +cell #[code Vocab] - +cell A storage container for lexical types. - - +row - +cell #[code rules] - +cell dict - +cell Exceptions and special-cases for the tokenizer. - - +row - +cell #[code prefix_search] - +cell callable - +cell - | A function matching the signature of - | #[code re.compile(string).search] to match prefixes. - - +row - +cell #[code suffix_search] - +cell callable - +cell - | A function matching the signature of - | #[code re.compile(string).search] to match suffixes. - - +row - +cell #[code infix_finditer] - +cell callable - +cell - | A function matching the signature of - | #[code re.compile(string).finditer] to find infixes. - - +footrow - +cell return - +cell #[code Tokenizer] - +cell The newly constructed object. - -+h(2, "init") Tokenizer.__init__ - +tag method - -p Create a #[code Tokenizer], to create #[code Doc] objects given unicode text. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code vocab] - +cell #[code Vocab] - +cell A storage container for lexical types. - - +row - +cell #[code rules] - +cell dict - +cell Exceptions and special-cases for the tokenizer. - - +row - +cell #[code prefix_search] - +cell callable - +cell - | A function matching the signature of - | #[code re.compile(string).search] to match prefixes. - - +row - +cell #[code suffix_search] - +cell callable - +cell - | A function matching the signature of - | #[code re.compile(string).search] to match suffixes. - - +row - +cell #[code infix_finditer] - +cell callable - +cell - | A function matching the signature of - | #[code re.compile(string).finditer] to find infixes. - - +footrow - +cell return - +cell #[code Tokenizer] - +cell The newly constructed object. - -+h(2, "call") Tokenizer.__call__ - +tag method - -p Tokenize a string. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code string] - +cell unicode - +cell The string to tokenize. - - +footrow - +cell return - +cell #[code Doc] - +cell A container for linguistic annotations. - -+h(2, "pipe") Tokenizer.pipe - +tag method - -p Tokenize a stream of texts. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code texts] - +cell - - +cell A sequence of unicode texts. - - +row - +cell #[code batch_size] - +cell int - +cell The number of texts to accumulate in an internal buffer. - - +row - +cell #[code n_threads] - +cell int - +cell - | The number of threads to use, if the implementation supports - | multi-threading. The default tokenizer is single-threaded. - - +footrow - +cell yield - +cell #[code Doc] - +cell A sequence of Doc objects, in order. - -+h(2, "find_infix") Tokenizer.find_infix - +tag method - -p Find internal split points of the string. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code string] - +cell unicode - +cell The string to split. - - +footrow - +cell return - +cell #[code List[re.MatchObject]] - +cell - | A list of objects that have #[code .start()] and #[code .end()] - | methods, denoting the placement of internal segment separators, - | e.g. hyphens. - -+h(2, "find_prefix") Tokenizer.find_prefix - +tag method - -p - | Find the length of a prefix that should be segmented from the string, or - | #[code None] if no prefix rules match. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code string] - +cell unicode - +cell The string to segment. - - +footrow - +cell return - +cell int / #[code None] - +cell The length of the prefix if present, otherwise #[code None]. - -+h(2, "find_suffix") Tokenizer.find_suffix - +tag method - -p - | Find the length of a suffix that should be segmented from the string, or - | #[code None] if no suffix rules match. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code string] - +cell unicode - +cell The string to segment. - - +footrow - +cell return - +cell int / #[code None] - +cell The length of the suffix if present, otherwise #[code None]. - -+h(2, "add_special_case") Tokenizer.add_special_case - +tag method - -p Add a special-case tokenization rule. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code string] - +cell unicode - +cell The string to specially tokenize. - - +row - +cell #[code token_attrs] - +cell - - +cell - | A sequence of dicts, where each dict describes a token and its - | attributes. The #[code ORTH] fields of the attributes must - | exactly match the string when they are concatenated. - - +footrow - +cell return - +cell #[code None] - +cell - diff --git a/website/docs/api/util.jade b/website/docs/api/util.jade index 97ed7c6e0..2127446df 100644 --- a/website/docs/api/util.jade +++ b/website/docs/api/util.jade @@ -1,12 +1,10 @@ -//- 💫 DOCS > API > ANNOTATION SPECS +//- 💫 DOCS > API > UTIL include ../../_includes/_mixins p | spaCy comes with a small collection of utility functions located in | #[+src(gh("spaCy", "spacy/util.py")) spacy/util.py]. - -+infobox("Important note") | Because utility functions are mostly intended for | #[strong internal use within spaCy], their behaviour may change with | future releases. The functions documented on this page should be safe @@ -14,7 +12,7 @@ p | recommend having additional tests in place if your application depends on | any of spaCy's utilities. -+h(2, "get_data_path") get_data_path ++h(2, "get_data_path") util.get_data_path +tag function p @@ -28,11 +26,11 @@ p +cell Only return path if it exists, otherwise return #[code None]. +footrow - +cell return + +cell returns +cell #[code Path] / #[code None] +cell Data path or #[code None]. -+h(2, "set_data_path") set_data_path ++h(2, "set_data_path") util.set_data_path +tag function p @@ -49,7 +47,7 @@ p +cell unicode or #[code Path] +cell Path to new data directory. -+h(2, "get_lang_class") get_lang_class ++h(2, "get_lang_class") util.get_lang_class +tag function p @@ -70,18 +68,27 @@ p +cell Two-letter language code, e.g. #[code 'en']. +footrow - +cell return + +cell returns +cell #[code Language] +cell Language class. -+h(2, "resolve_model_path") resolve_model_path ++h(2, "load_model") util.load_model +tag function + +tag-new(2) -p Resolve a model name or string to a model path. +p + | Load a model from a shortcut link, package or data path. If called with a + | shortcut link or package name, spaCy will assume the model is a Python + | package and import and call its #[code load()] method. If called with a + | path, spaCy will assume it's a data directory, read the language and + | pipeline settings from the meta.json and initialise a #[code Language] + | class. The model data will then be loaded in via + | #[+api("language#from_disk") #[code Language.from_disk()]]. +aside-code("Example"). - model_path = util.resolve_model_path('en') - model_path = util.resolve_model_path('/path/to/en') + nlp = util.load_model('en') + nlp = util.load_model('en_core_web_sm', disable=['ner']) + nlp = util.load_model('/path/to/data') +table(["Name", "Type", "Description"]) +row @@ -89,12 +96,106 @@ p Resolve a model name or string to a model path. +cell unicode +cell Package name, shortcut link or model path. + +row + +cell #[code **overrides] + +cell - + +cell Specific overrides, like pipeline components to disable. + +footrow - +cell return - +cell #[code Path] + +cell returns + +cell #[code Language] + +cell #[code Language] class with the loaded model. + ++h(2, "load_model_from_path") util.load_model_from_path + +tag function + +tag-new(2) + +p + | Load a model from a data directory path. Creates the + | #[+api("language") #[code Language]] class and pipeline based on the + | directory's meta.json and then calls + | #[+api("language#from_disk") #[code from_disk()]] with the path. This + | function also makes it easy to test a new model that you haven't packaged + | yet. + ++aside-code("Example"). + nlp = load_model_from_path('/path/to/data') + ++table(["Name", "Type", "Description"]) + +row + +cell #[code model_path] + +cell unicode +cell Path to model data directory. -+h(2, "is_package") is_package + +row + +cell #[code meta] + +cell dict + +cell + | Model meta data. If #[code False], spaCy will try to load the + | meta from a meta.json in the same directory. + + +row + +cell #[code **overrides] + +cell - + +cell Specific overrides, like pipeline components to disable. + + +footrow + +cell returns + +cell #[code Language] + +cell #[code Language] class with the loaded model. + ++h(2, "load_model_from_init_py") util.load_model_from_init_py + +tag function + +tag-new(2) + +p + | A helper function to use in the #[code load()] method of a model package's + | #[+src(gh("spacy-dev-resources", "templates/model/en_model_name/__init__.py")) __init__.py]. + ++aside-code("Example"). + from spacy.util import load_model_from_init_py + + def load(**overrides): + return load_model_from_init_py(__file__, **overrides) + ++table(["Name", "Type", "Description"]) + +row + +cell #[code init_file] + +cell unicode + +cell Path to model's __init__.py, i.e. #[code __file__]. + + +row + +cell #[code **overrides] + +cell - + +cell Specific overrides, like pipeline components to disable. + + +footrow + +cell returns + +cell #[code Language] + +cell #[code Language] class with the loaded model. + ++h(2, "get_model_meta") util.get_model_meta + +tag function + +tag-new(2) + +p + | Get a model's meta.json from a directory path and validate its contents. + ++aside-code("Example"). + meta = util.get_model_meta('/path/to/model') + ++table(["Name", "Type", "Description"]) + +row + +cell #[code path] + +cell unicode or #[code Path] + +cell Path to model directory. + + +footrow + +cell returns + +cell dict + +cell The model's meta data. + ++h(2, "is_package") util.is_package +tag function p @@ -112,20 +213,22 @@ p +cell Name of package. +footrow - +cell return + +cell returns +cell #[code bool] +cell #[code True] if installed package, #[code False] if not. -+h(2, "get_model_package_path") get_model_package_path ++h(2, "get_package_path") util.get_package_path +tag function + +tag-new(2) p - | Get path to a #[+a("/docs/usage/models") model package] installed via pip. - | Currently imports the package to find it and parse its meta data. + | Get path to an installed package. Mainly used to resolve the location of + | #[+a("/docs/usage/models") model packages]. Currently imports the package + | to find its path. +aside-code("Example"). - util.get_model_package_path('en_core_web_sm') - # /usr/lib/python3.6/site-packages/en_core_web_sm/en_core_web_sm-1.2.0 + util.get_package_path('en_core_web_sm') + # /usr/lib/python3.6/site-packages/en_core_web_sm +table(["Name", "Type", "Description"]) +row @@ -134,40 +237,32 @@ p +cell Name of installed package. +footrow - +cell return - +cell #[code Path] - +cell Path to model data directory. - -+h(2, "parse_package_meta") parse_package_meta - +tag function - -p - | Check if a #[code meta.json] exists in a model package and return its - | contents. - -+aside-code("Example"). - if util.is_package('en_core_web_sm'): - path = util.get_model_package_path('en_core_web_sm') - meta = util.parse_package_meta(path, require=True) - # {'name': 'core_web_sm', 'lang': 'en', ...} - -+table(["Name", "Type", "Description"]) - +row - +cell #[code package_path] + +cell returns +cell #[code Path] +cell Path to model package directory. - +row - +cell #[code require] - +cell #[code bool] - +cell If #[code True], raise error if no #[code meta.json] is found. ++h(2, "is_in_jupyter") util.is_in_jupyter + +tag function + +tag-new(2) +p + | Check if user is running spaCy from a #[+a("https://jupyter.org") Jupyter] + | notebook by detecting the IPython kernel. Mainly used for the + | #[+api("displacy") #[code displacy]] visualizer. + ++aside-code("Example"). + html = '<h1>Hello world!</h1>' + if util.is_in_jupyter(): + from IPython.core.display import display, HTML + return display(HTML(html)) + ++table(["Name", "Type", "Description"]) +footrow - +cell return - +cell dict / #[code None] - +cell Model meta data or #[code None]. + +cell returns + +cell bool + +cell #[code True] if in Jupyter, #[code False] if not. -+h(2, "update_exc") update_exc ++h(2, "update_exc") util.update_exc +tag function p @@ -194,24 +289,25 @@ p +cell Exception dictionaries to add to the base exceptions, in order. +footrow - +cell return + +cell returns +cell dict +cell Combined tokenizer exceptions. -+h(2, "prints") prints ++h(2, "prints") util.prints +tag function + +tag-new(2) p | Print a formatted, text-wrapped message with optional title. If a text | argument is a #[code Path], it's converted to a string. Should only - | be used for interactive components like the #[+a("/docs/usage/cli") CLI]. + | be used for interactive components like the #[+api("cli") cli]. +aside-code("Example"). data_path = Path('/some/path') if not path.exists(): util.prints("Can't find the path.", data_path, - title="Error", exits=True) + title="Error", exits=1) +table(["Name", "Type", "Description"]) +row @@ -223,5 +319,6 @@ p +cell #[code **kwargs] +cell - +cell - | #[code title] is rendered as coloured headline. #[code exits=True] - | performs system exit after printing. + | #[code title] is rendered as coloured headline. #[code exits] + | performs system exit after printing, using the value of the + | argument as the exit code, e.g. #[code exits=1]. diff --git a/website/docs/api/vectors.jade b/website/docs/api/vectors.jade new file mode 100644 index 000000000..ef9aa2b52 --- /dev/null +++ b/website/docs/api/vectors.jade @@ -0,0 +1,7 @@ +//- 💫 DOCS > API > VECTORS + +include ../../_includes/_mixins + +p A container class for vector data keyed by string. + ++under-construction diff --git a/website/docs/api/vocab.jade b/website/docs/api/vocab.jade index 7490bccf4..4d3e0828a 100644 --- a/website/docs/api/vocab.jade +++ b/website/docs/api/vocab.jade @@ -3,63 +3,10 @@ include ../../_includes/_mixins p - | A look-up table that allows you to access #[code Lexeme] objects. The + | A lookup table that allows you to access #[code Lexeme] objects. The | #[code Vocab] instance also provides access to the #[code StringStore], | and owns underlying C-data that is shared between #[code Doc] objects. -+h(2, "attributes") Attributes - -+table(["Name", "Type", "Description"]) - +row - +cell #[code strings] - +cell #[code StringStore] - +cell A table managing the string-to-int mapping. - - +row - +cell #[code vectors_length] - +cell int - +cell The dimensionality of the word vectors, if present. - -+h(2, "load") Vocab.load - +tag classmethod - -p Load the vocabulary from a path. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code path] - +cell #[code Path] - +cell The path to load from. - - +row - +cell #[code lex_attr_getters] - +cell dict - +cell - | A dictionary mapping attribute IDs to functions to compute them. - | Defaults to #[code None]. - - +row - +cell #[code lemmatizer] - +cell - - +cell A lemmatizer. Defaults to #[code None]. - - +row - +cell #[code tag_map] - +cell dict - +cell - | A dictionary mapping fine-grained tags to coarse-grained - | parts-of-speech, and optionally morphological attributes. - - +row - +cell #[code oov_prob] - +cell float - +cell The default probability for out-of-vocabulary words. - - +footrow - +cell return - +cell #[code Vocab] - +cell The newly constructed object. - +h(2, "init") Vocab.__init__ +tag method @@ -73,11 +20,6 @@ p Create the vocabulary. | A dictionary mapping attribute IDs to functions to compute them. | Defaults to #[code None]. - +row - +cell #[code lemmatizer] - +cell - - +cell A lemmatizer. Defaults to #[code None]. - +row +cell #[code tag_map] +cell dict @@ -86,23 +28,34 @@ p Create the vocabulary. | parts-of-speech, and optionally morphological attributes. +row - +cell #[code oov_prob] - +cell float - +cell The default probability for out-of-vocabulary words. + +cell #[code lemmatizer] + +cell object + +cell A lemmatizer. Defaults to #[code None]. + + +row + +cell #[code strings] + +cell #[code StringStore] or list + +cell + | A #[+api("stringstore") #[code StringStore]] that maps + | strings to hash values, and vice versa, or a list of strings. +footrow - +cell return + +cell returns +cell #[code Vocab] +cell The newly constructed object. +h(2, "len") Vocab.__len__ +tag method -p Get the number of lexemes in the vocabulary. +p Get the current number of lexemes in the vocabulary. + ++aside-code("Example"). + doc = nlp(u'This is a sentence.') + assert len(nlp.vocab) > 0 +table(["Name", "Type", "Description"]) +footrow - +cell return + +cell returns +cell int +cell The number of lexems in the vocabulary. @@ -113,32 +66,48 @@ p | Retrieve a lexeme, given an int ID or a unicode string. If a previously | unseen unicode string is given, a new lexeme is created and stored. ++aside-code("Example"). + apple = nlp.vocab.strings['apple'] + assert nlp.vocab[apple] == nlp.vocab[u'apple'] + +table(["Name", "Type", "Description"]) +row +cell #[code id_or_string] +cell int / unicode - +cell The integer ID of a word, or its unicode string. + +cell The hash value of a word, or its unicode string. +footrow - +cell return + +cell returns +cell #[code Lexeme] +cell The lexeme indicated by the given ID. -+h(2, "iter") Span.__iter__ ++h(2, "iter") Vocab.__iter__ +tag method p Iterate over the lexemes in the vocabulary. ++aside-code("Example"). + stop_words = (lex for lex in nlp.vocab if lex.is_stop) + +table(["Name", "Type", "Description"]) +footrow - +cell yield + +cell yields +cell #[code Lexeme] +cell An entry in the vocabulary. +h(2, "contains") Vocab.__contains__ +tag method -p Check whether the string has an entry in the vocabulary. +p + | Check whether the string has an entry in the vocabulary. To get the ID + | for a given string, you need to look it up in + | #[+api("vocab#attributes") #[code vocab.strings]]. + ++aside-code("Example"). + apple = nlp.vocab.strings['apple'] + oov = nlp.vocab.strings['dskfodkfos'] + assert apple in nlp.vocab + assert oov not in nlp.vocab +table(["Name", "Type", "Description"]) +row @@ -147,32 +116,27 @@ p Check whether the string has an entry in the vocabulary. +cell The ID string. +footrow - +cell return + +cell returns +cell bool +cell Whether the string has an entry in the vocabulary. -+h(2, "resize_vectors") Vocab.resize_vectors - +tag method - -p - | Set #[code vectors_length] to a new size, and allocate more memory for - | the #[code Lexeme] vectors if necessary. The memory will be zeroed. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code new_size] - +cell int - +cell The new size of the vectors. - - +footrow - +cell return - +cell #[code None] - +cell - - +h(2, "add_flag") Vocab.add_flag +tag method -p Set a new boolean flag to words in the vocabulary. +p + | Set a new boolean flag to words in the vocabulary. The #[code flag_getter] + | function will be called over the words currently in the vocab, and then + | applied to new words as they occur. You'll then be able to access the flag + | value on each token, using #[code token.check_flag(flag_id)]. + ++aside-code("Example"). + def is_my_product(text): + products = [u'spaCy', u'Thinc', u'displaCy'] + return text in products + + MY_PRODUCT = nlp.vocab.add_flag(is_my_product) + doc = nlp(u'I like spaCy') + assert doc[2].check_flag(MY_PRODUCT) == True +table(["Name", "Type", "Description"]) +row @@ -189,90 +153,106 @@ p Set a new boolean flag to words in the vocabulary. | available bit will be chosen. +footrow - +cell return + +cell returns +cell int +cell The integer ID by which the flag value can be checked. -+h(2, "dump") Vocab.dump ++h(2, "to_disk") Vocab.to_disk +tag method + +tag-new(2) -p Save the lexemes binary data to the given location. +p Save the current state to a directory. + ++aside-code("Example"). + nlp.vocab.to_disk('/path/to/vocab') +table(["Name", "Type", "Description"]) +row - +cell #[code loc] - +cell #[code Path] - +cell The path to load from. - - +footrow - +cell return - +cell #[code None] - +cell - - -+h(2, "load_lexemes") Vocab.load_lexemes - +tag method - -p - -+table(["Name", "Type", "Description"]) - +row - +cell #[code loc] - +cell unicode - +cell Path to load the lexemes.bin file from. - - +footrow - +cell return - +cell #[code None] - +cell - - -+h(2, "dump_vectors") Vocab.dump_vectors - +tag method - -p Save the word vectors to a binary file. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code loc] - +cell #[code Path] - +cell The path to save to. - - +footrow - +cell return - +cell #[code None] - +cell - - -+h(2, "load_vectors") Vocab.load_vectors - +tag method - -p Load vectors from a text-based file. - -+table(["Name", "Type", "Description"]) - +row - +cell #[code file_] - +cell buffer + +cell #[code path] + +cell unicode or #[code Path] +cell - | The file to read from. Entries should be separated by newlines, - | and each entry should be whitespace delimited. The first value - | of the entry should be the word string, and subsequent entries - | should be the values of the vector. + | A path to a directory, which will be created if it doesn't exist. + | Paths may be either strings or #[code Path]-like objects. - +footrow - +cell return - +cell int - +cell The length of the vectors loaded. - -+h(2, "load_vectors_from_bin_loc") Vocab.load_vectors_from_bin_loc ++h(2, "from_disk") Vocab.from_disk +tag method + +tag-new(2) -p Load vectors from the location of a binary file. +p Loads state from a directory. Modifies the object in place and returns it. + ++aside-code("Example"). + from spacy.vocab import Vocab + vocab = Vocab().from_disk('/path/to/vocab') +table(["Name", "Type", "Description"]) +row - +cell #[code loc] - +cell unicode - +cell The path of the binary file to load from. + +cell #[code path] + +cell unicode or #[code Path] + +cell + | A path to a directory. Paths may be either strings or + | #[code Path]-like objects. +footrow - +cell return - +cell int - +cell The length of the vectors loaded. + +cell returns + +cell #[code Vocab] + +cell The modified #[code Vocab] object. + ++h(2, "to_bytes") Vocab.to_bytes + +tag method + +p Serialize the current state to a binary string. + ++aside-code("Example"). + vocab_bytes = nlp.vocab.to_bytes() + ++table(["Name", "Type", "Description"]) + +row + +cell #[code **exclude] + +cell - + +cell Named attributes to prevent from being serialized. + + +footrow + +cell returns + +cell bytes + +cell The serialized form of the #[code Vocab] object. + ++h(2, "from_bytes") Vocab.from_bytes + +tag method + +p Load state from a binary string. + ++aside-code("Example"). + fron spacy.vocab import Vocab + vocab_bytes = nlp.vocab.to_bytes() + vocab = Vocab() + vocab.from_bytes(vocab_bytes) + ++table(["Name", "Type", "Description"]) + +row + +cell #[code bytes_data] + +cell bytes + +cell The data to load from. + + +row + +cell #[code **exclude] + +cell - + +cell Named attributes to prevent from being loaded. + + +footrow + +cell returns + +cell #[code Vocab] + +cell The #[code Vocab] object. + ++h(2, "attributes") Attributes + ++aside-code("Example"). + apple_id = nlp.vocab.strings['apple'] + assert type(apple_id) == int + PERSON = nlp.vocab.strings['PERSON'] + assert type(PERSON) == int + ++table(["Name", "Type", "Description"]) + +row + +cell #[code strings] + +cell #[code StringStore] + +cell A table managing the string-to-int mapping. diff --git a/website/docs/usage/_data.json b/website/docs/usage/_data.json index a3d37b833..c8373a095 100644 --- a/website/docs/usage/_data.json +++ b/website/docs/usage/_data.json @@ -3,28 +3,26 @@ "Get started": { "Installation": "./", "Models": "models", + "spaCy 101": "spacy-101", "Lightning tour": "lightning-tour", - "Visualizers": "visualizers", - "Troubleshooting": "troubleshooting", "What's new in v2.0": "v2" }, - "Workflows": { - "spaCy 101": "spacy-101", - "Loading the pipeline": "language-processing-pipeline", - "Processing text": "processing-text", - "spaCy's data model": "data-model", + "Guides": { "POS tagging": "pos-tagging", "Using the parse": "dependency-parse", "Entity recognition": "entity-recognition", - "Custom pipelines": "customizing-pipeline", - "Rule-based matching": "rule-based-matching", - "Word vectors": "word-vectors-similarities", - "Deep learning": "deep-learning", + "Vectors & similarity": "word-vectors-similarities", "Custom tokenization": "customizing-tokenizer", + "Rule-based matching": "rule-based-matching", "Adding languages": "adding-languages", + "Processing pipelines": "language-processing-pipeline", + "Text classification": "text-classification", + "Deep learning": "deep-learning", + "Production use": "production-use", "Training": "training", "Training NER": "training-ner", - "Saving & loading": "saving-loading" + "Saving & loading": "saving-loading", + "Visualizers": "visualizers" }, "Examples": { "Tutorials": "tutorials", @@ -38,54 +36,35 @@ "quickstart": true }, - "v2": { - "title": "What's new in v2.0" - }, - "models": { "title": "Models", - "next": "lightning-tour" + "next": "spacy-101", + "quickstart": true + }, + + "spacy-101": { + "title": "spaCy 101 – Everything you need to know", + "next": "lightning-tour", + "quickstart": true, + "preview": "101" }, "lightning-tour": { "title": "Lightning tour", - "next": "spacy-101" + "next": "v2" }, "visualizers": { "title": "Visualizers" }, - "troubleshooting": { - "title": "Troubleshooting", - "next": "resources" + "v2": { + "title": "What's new in v2.0" }, - "resources": { - "title": "Resources" - }, - - "spacy-101": { - "title": "spaCy 101" - }, - - "language-processing-pipeline": { - "title": "Loading a language processing pipeline", - "next": "processing-text" - }, - - "customizing-pipeline": { - "title": "Customizing the pipeline", - "next": "customizing-tokenizer" - }, - - "processing-text": { - "title": "Processing text", - "next": "data-model" - }, - - "data-model": { - "title": "Understanding spaCy's data model" + "pos-tagging": { + "title": "Part-of-speech tagging", + "next": "dependency-parse" }, "dependency-parse": { @@ -94,25 +73,48 @@ }, "entity-recognition": { - "title": "Entity recognition", + "title": "Named Entity Recognition", + "next": "training-ner" + }, + + "word-vectors-similarities": { + "title": "Using word vectors and semantic similarities", + "next": "customizing-tokenizer" + }, + + "customizing-tokenizer": { + "title": "Customising the tokenizer", "next": "rule-based-matching" }, "rule-based-matching": { - "title": "Rule-based matching" + "title": "Rule-based matching", + "next": "adding-languages" }, - "word-vectors-similarities": { - "title": "Using word vectors and semantic similarities" + "adding-languages": { + "title": "Adding languages", + "next": "training" + }, + + "language-processing-pipeline": { + "title": "Language processing pipelines", + "next": "deep-learning" }, "deep-learning": { - "title": "Hooking a deep learning model into spaCy" + "title": "Hooking a deep learning model into spaCy", + "next": "production use" }, - "customizing-tokenizer": { - "title": "Customizing the tokenizer", - "next": "adding-languages" + "text-classification": { + "title": "Text classification", + "next": "training" + }, + + "production-use": { + "title": "Production use", + "next": "training" }, "training": { @@ -126,17 +128,7 @@ }, "saving-loading": { - "title": "Saving and loading models" - }, - - "pos-tagging": { - "title": "Part-of-speech tagging", - "next": "dependency-parse" - }, - - "adding-languages": { - "title": "Adding languages", - "next": "training" + "title": "Saving, loading and data serialization" }, "showcase": { diff --git a/website/docs/usage/_models-list.jade b/website/docs/usage/_models-list.jade index 942de28c4..195df9f56 100644 --- a/website/docs/usage/_models-list.jade +++ b/website/docs/usage/_models-list.jade @@ -19,9 +19,6 @@ p | View model releases +table(["Name", "Language", "Voc", "Dep", "Ent", "Vec", "Size", "License"]) - +model-row("en_core_web_sm", "English", [1, 1, 1, 1], "50 MB", "CC BY-SA", true) - +model-row("en_core_web_md", "English", [1, 1, 1, 1], "1 GB", "CC BY-SA") - +model-row("en_depent_web_md", "English", [1, 1, 1, 0], "328 MB", "CC BY-SA") - +model-row("en_vectors_glove_md", "English", [1, 0, 0, 1], "727 MB", "CC BY-SA") - +model-row("de_core_news_md", "German", [1, 1, 1, 1], "645 MB", "CC BY-SA", true, true) - +model-row("fr_depvec_web_lg", "French", [1, 1, 0, 1], "1.33 GB", "CC BY-NC", true, true) + for models, lang in MODELS + for model, i in models + +model-row(model.id, model.lang, model.feats, model.size, model.license, model.def || models.length == 1, i == 0) diff --git a/website/docs/usage/_spacy-101/_architecture.jade b/website/docs/usage/_spacy-101/_architecture.jade new file mode 100644 index 000000000..c5a85f0b0 --- /dev/null +++ b/website/docs/usage/_spacy-101/_architecture.jade @@ -0,0 +1,126 @@ +//- 💫 DOCS > USAGE > SPACY 101 > ARCHITECTURE + +p + | The central data structures in spaCy are the #[code Doc] and the + | #[code Vocab]. The #[code Doc] object owns the + | #[strong sequence of tokens] and all their annotations. The #[code Vocab] + | object owns a set of #[strong look-up tables] that make common + | information available across documents. By centralising strings, word + | vectors and lexical attributes, we avoid storing multiple copies of this + | data. This saves memory, and ensures there's a + | #[strong single source of truth]. + +p + | Text annotations are also designed to allow a single source of truth: the + | #[code Doc] object owns the data, and #[code Span] and #[code Token] are + | #[strong views that point into it]. The #[code Doc] object is constructed + | by the #[code Tokenizer], and then #[strong modified in place] by the + | components of the pipeline. The #[code Language] object coordinates these + | components. It takes raw text and sends it through the pipeline, + | returning an #[strong annotated document]. It also orchestrates training + | and serialization. + ++image + include ../../../assets/img/docs/architecture.svg + .u-text-right + +button("/assets/img/docs/architecture.svg", false, "secondary").u-text-tag View large graphic + ++table(["Name", "Description"]) + +row + +cell #[+api("language") #[code Language]] + +cell + | A text-processing pipeline. Usually you'll load this once per + | process as #[code nlp] and pass the instance around your application. + + +row + +cell #[+api("doc") #[code Doc]] + +cell A container for accessing linguistic annotations. + + +row + +cell #[+api("span") #[code Span]] + +cell A slice from a #[code Doc] object. + + +row + +cell #[+api("token") #[code Token]] + +cell + | An individual token — i.e. a word, punctuation symbol, whitespace, + | etc. + + +row + +cell #[+api("lexeme") #[code Lexeme]] + +cell + | An entry in the vocabulary. It's a word type with no context, as + | opposed to a word token. It therefore has no part-of-speech tag, + | dependency parse etc. + + +row + +cell #[+api("vocab") #[code Vocab]] + +cell + | A lookup table for the vocabulary that allows you to access + | #[code Lexeme] objects. + + +row + +cell #[code Morphology] + +cell + | Assign linguistic features like lemmas, noun case, verb tense etc. + | based on the word and its part-of-speech tag. + + +row + +cell #[+api("stringstore") #[code StringStore]] + +cell Map strings to and from hash values. + + +row + +cell #[+api("tokenizer") #[code Tokenizer]] + +cell + | Segment text, and create #[code Doc] objects with the discovered + | segment boundaries. + + +row + +cell #[code Lemmatizer] + +cell + | Determine the base forms of words. + + +row + +cell #[+api("matcher") #[code Matcher]] + +cell + | Match sequences of tokens, based on pattern rules, similar to + | regular expressions. + + ++h(3, "architecture-pipeline") Pipeline components + ++table(["Name", "Description"]) + +row + +cell #[+api("tagger") #[code Tagger]] + +cell Annotate part-of-speech tags on #[code Doc] objects. + + +row + +cell #[+api("dependencyparser") #[code DependencyParser]] + +cell Annotate syntactic dependencies on #[code Doc] objects. + + +row + +cell #[+api("entityrecognizer") #[code EntityRecognizer]] + +cell + | Annotate named entities, e.g. persons or products, on #[code Doc] + | objects. + ++h(3, "architecture-other") Other classes + ++table(["Name", "Description"]) + +row + +cell #[+api("vectors") #[code Vectors]] + +cell Container class for vector data keyed by string. + + +row + +cell #[+api("binder") #[code Binder]] + +cell Container class for serializing collections of #[code Doc] objects. + + +row + +cell #[+api("goldparse") #[code GoldParse]] + +cell Collection for training annotations. + + +row + +cell #[+api("goldcorpus") #[code GoldCorpus]] + +cell + | An annotated corpus, using the JSON file format. Manages + | annotations for tagging, dependency parsing and NER. diff --git a/website/docs/usage/_spacy-101/_language-data.jade b/website/docs/usage/_spacy-101/_language-data.jade new file mode 100644 index 000000000..aaca10ebb --- /dev/null +++ b/website/docs/usage/_spacy-101/_language-data.jade @@ -0,0 +1,109 @@ +//- 💫 DOCS > USAGE > SPACY 101 > LANGUAGE DATA + +p + | Every language is different – and usually full of + | #[strong exceptions and special cases], especially amongst the most + | common words. Some of these exceptions are shared across languages, while + | others are #[strong entirely specific] – usually so specific that they need + | to be hard-coded. The #[+src(gh("spaCy", "spacy/lang")) lang] module + | contains all language-specific data, organised in simple Python files. + | This makes the data easy to update and extend. + +p + | The #[strong shared language data] in the directory root includes rules + | that can be generalised across languages – for example, rules for basic + | punctuation, emoji, emoticons, single-letter abbreviations and norms for + | equivalent tokens with different spellings, like #[code "] and + | #[code ”]. This helps the models make more accurate predictions. + | The #[strong individual language data] in a submodule contains + | rules that are only relevant to a particular language. It also takes + | care of putting together all components and creating the #[code Language] + | subclass – for example, #[code English] or #[code German]. + ++aside-code. + from spacy.lang.en import English + from spacy.lang.en import German + + nlp_en = English() # includes English data + nlp_de = German() # includes German data + ++image + include ../../../assets/img/docs/language_data.svg + .u-text-right + +button("/assets/img/docs/language_data.svg", false, "secondary").u-text-tag View large graphic + ++table(["Name", "Description"]) + +row + +cell #[strong Stop words]#[br] + | #[+src(gh("spacy-dev-resources", "templates/new_language/stop_words.py")) stop_words.py] + +cell + | List of most common words of a language that are often useful to + | filter out, for example "and" or "I". Matching tokens will + | return #[code True] for #[code is_stop]. + + +row + +cell #[strong Tokenizer exceptions]#[br] + | #[+src(gh("spacy-dev-resources", "templates/new_language/tokenizer_exceptions.py")) tokenizer_exceptions.py] + +cell + | Special-case rules for the tokenizer, for example, contractions + | like "can't" and abbreviations with punctuation, like "U.K.". + + +row + +cell #[strong Norm exceptions] + | #[+src(gh("spaCy", "spacy/lang/norm_exceptions.py")) norm_exceptions.py] + +cell + | Special-case rules for normalising tokens to improve the model's + | predictions, for example on American vs. British spelling. + + +row + +cell #[strong Punctuation rules] + | #[+src(gh("spaCy", "spacy/lang/punctuation.py")) punctuation.py] + +cell + | Regular expressions for splitting tokens, e.g. on punctuation or + | special characters like emoji. Includes rules for prefixes, + | suffixes and infixes. + + +row + +cell #[strong Character classes] + | #[+src(gh("spaCy", "spacy/lang/char_classes.py")) char_classes.py] + +cell + | Character classes to be used in regular expressions, for example, + | latin characters, quotes, hyphens or icons. + + +row + +cell #[strong Lexical attributes] + | #[+src(gh("spacy-dev-resources", "templates/new_language/lex_attrs.py")) lex_attrs.py] + +cell + | Custom functions for setting lexical attributes on tokens, e.g. + | #[code like_num], which includes language-specific words like "ten" + | or "hundred". + + +row + +cell #[strong Syntax iterators] + | #[+src(gh("spaCy", "spacy/lang/en/syntax_iterators.py")) syntax_iterators.py] + +cell + | Functions that compute views of a #[code Doc] object based on its + | syntax. At the moment, only used for + | #[+a("/docs/usage/dependency-parse#noun-chunks") noun chunks]. + + +row + +cell #[strong Lemmatizer] + | #[+src(gh("spacy-dev-resources", "templates/new_language/lemmatizer.py")) lemmatizer.py] + +cell + | Lemmatization rules or a lookup-based lemmatization table to + | assign base forms, for example "be" for "was". + + +row + +cell #[strong Tag map]#[br] + | #[+src(gh("spacy-dev-resources", "templates/new_language/tag_map.py")) tag_map.py] + +cell + | Dictionary mapping strings in your tag set to + | #[+a("http://universaldependencies.org/u/pos/all.html") Universal Dependencies] + | tags. + + +row + +cell #[strong Morph rules] + | #[+src(gh("spaCy", "spacy/lang/en/morph_rules.py")) morph_rules.py] + +cell + | Exception rules for morphological analysis of irregular words like + | personal pronouns. diff --git a/website/docs/usage/_spacy-101/_named-entities.jade b/website/docs/usage/_spacy-101/_named-entities.jade new file mode 100644 index 000000000..a3c539564 --- /dev/null +++ b/website/docs/usage/_spacy-101/_named-entities.jade @@ -0,0 +1,38 @@ +//- 💫 DOCS > USAGE > SPACY 101 > NAMED ENTITIES + +p + | A named entity is a "real-world object" that's assigned a name – for + | example, a person, a country, a product or a book title. spaCy can + | #[strong recognise] #[+a("/docs/api/annotation#named-entities") various types] + | of named entities in a document, by asking the model for a + | #[strong prediction]. Because models are statistical and strongly depend + | on the examples they were trained on, this doesn't always work + | #[em perfectly] and might need some tuning later, depending on your use + | case. + +p + | Named entities are available as the #[code ents] property of a #[code Doc]: + ++code. + doc = nlp(u'Apple is looking at buying U.K. startup for $1 billion') + + for ent in doc.ents: + print(ent.text, ent.start_char, ent.end_char, ent.label_) + ++aside + | #[strong Text]: The original entity text.#[br] + | #[strong Start]: Index of start of entity in the #[code Doc].#[br] + | #[strong End]: Index of end of entity in the #[code Doc].#[br] + | #[strong Label]: Entity label, i.e. type. + ++table(["Text", "Start", "End", "Label", "Description"]) + - var style = [0, 1, 1, 1, 0] + +annotation-row(["Apple", 0, 5, "ORG", "Companies, agencies, institutions."], style) + +annotation-row(["U.K.", 27, 31, "GPE", "Geopolitical entity, i.e. countries, cities, states."], style) + +annotation-row(["$1 billion", 44, 54, "MONEY", "Monetary values, including unit."], style) + +p + | Using spaCy's built-in #[+a("/docs/usage/visualizers") displaCy visualizer], + | here's what our example sentence and its named entities look like: + ++codepen("2f2ad1408ff79fc6a326ea3aedbb353b", 160) diff --git a/website/docs/usage/_spacy-101/_pipelines.jade b/website/docs/usage/_spacy-101/_pipelines.jade new file mode 100644 index 000000000..c21c9f97c --- /dev/null +++ b/website/docs/usage/_spacy-101/_pipelines.jade @@ -0,0 +1,78 @@ +//- 💫 DOCS > USAGE > SPACY 101 > PIPELINES + +p + | When you call #[code nlp] on a text, spaCy first tokenizes the text to + | produce a #[code Doc] object. The #[code Doc] is then processed in several + | different steps – this is also referred to as the + | #[strong processing pipeline]. The pipeline used by the + | #[+a("/docs/usage/models") default models] consists of a + | tensorizer, a tagger, a parser and an entity recognizer. Each pipeline + | component returns the processed #[code Doc], which is then passed on to + | the next component. + ++image + include ../../../assets/img/docs/pipeline.svg + .u-text-right + +button("/assets/img/docs/pipeline.svg", false, "secondary").u-text-tag View large graphic + ++aside + | #[strong Name:] ID of the pipeline component.#[br] + | #[strong Component:] spaCy's implementation of the component.#[br] + | #[strong Creates:] Objects, attributes and properties modified and set by + | the component. + ++table(["Name", "Component", "Creates", "Description"]) + +row + +cell tokenizer + +cell #[+api("tokenizer") #[code Tokenizer]] + +cell #[code Doc] + +cell Segment text into tokens. + + +row("divider") + +cell tensorizer + +cell #[code TokenVectorEncoder] + +cell #[code Doc.tensor] + +cell Create feature representation tensor for #[code Doc]. + + +row + +cell tagger + +cell #[+api("tagger") #[code Tagger]] + +cell #[code Doc[i].tag] + +cell Assign part-of-speech tags. + + +row + +cell parser + +cell #[+api("dependencyparser") #[code DependencyParser]] + +cell + | #[code Doc[i].head], #[code Doc[i].dep], #[code Doc.sents], + | #[code Doc.noun_chunks] + +cell Assign dependency labels. + + +row + +cell ner + +cell #[+api("entityrecognizer") #[code EntityRecognizer]] + +cell #[code Doc.ents], #[code Doc[i].ent_iob], #[code Doc[i].ent_type] + +cell Detect and label named entities. + +p + | The processing pipeline always #[strong depends on the statistical model] + | and its capabilities. For example, a pipeline can only include an entity + | recognizer component if the model includes data to make predictions of + | entity labels. This is why each model will specify the pipeline to use + | in its meta data, as a simple list containing the component names: + ++code(false, "json"). + "pipeline": ["tensorizer", "tagger", "parser", "ner"] + +p + | Although you can mix and match pipeline components, their + | #[strong order and combination] is usually important. Some components may + | require certain modifications on the #[code Doc] to process it. For + | example, the default pipeline first applies the tensorizer, which + | pre-processes the doc and encodes its internal + | #[strong meaning representations] as an array of floats, also called a + | #[strong tensor]. This includes the tokens and their context, which is + | required for the next component, the tagger, to make predictions of the + | part-of-speech tags. Because spaCy's models are neural network models, + | they only "speak" tensors and expect the input #[code Doc] to have + | a #[code tensor]. diff --git a/website/docs/usage/_spacy-101/_pos-deps.jade b/website/docs/usage/_spacy-101/_pos-deps.jade new file mode 100644 index 000000000..52a7fdd3c --- /dev/null +++ b/website/docs/usage/_spacy-101/_pos-deps.jade @@ -0,0 +1,62 @@ +//- 💫 DOCS > USAGE > SPACY 101 > POS TAGGING AND DEPENDENCY PARSING + +p + | After tokenization, spaCy can also #[strong parse] and #[strong tag] a + | given #[code Doc]. This is where the statistical model comes in, which + | enables spaCy to #[strong make a prediction] of which tag or label most + | likely applies in this context. A model consists of binary data and is + | produced by showing a system enough examples for it to make predictions + | that generalise across the language – for example, a word following "the" + | in English is most likely a noun. + +p + | Linguistic annotations are available as + | #[+api("token#attributes") #[code Token] attributes]. Like many NLP + | libraries, spaCy #[strong encodes all strings to hash values] to reduce + | memory usage and improve efficiency. So to get the readable string + | representation of an attribute, we need to add an underscore #[code _] + | to its name: + ++code. + doc = nlp(u'Apple is looking at buying U.K. startup for $1 billion') + + for token in doc: + print(token.text, token.lemma_, token.pos_, token.tag_, token.dep_, + token.shape_, token.is_alpha, token.is_stop) + ++aside + | #[strong Text:] The original word text.#[br] + | #[strong Lemma:] The base form of the word.#[br] + | #[strong POS:] The simple part-of-speech tag.#[br] + | #[strong Tag:] The detailed part-of-speech tag.#[br] + | #[strong Dep:] Syntactic dependency, i.e. the relation between tokens.#[br] + | #[strong Shape:] The word shape – capitalisation, punctuation, digits.#[br] + | #[strong is alpha:] Is the token an alpha character?#[br] + | #[strong is stop:] Is the token part of a stop list, i.e. the most common + | words of the language?#[br] + ++table(["Text", "Lemma", "POS", "Tag", "Dep", "Shape", "alpha", "stop"]) + - var style = [0, 0, 1, 1, 1, 1, 1, 1] + +annotation-row(["Apple", "apple", "PROPN", "NNP", "nsubj", "Xxxxx", true, false], style) + +annotation-row(["is", "be", "VERB", "VBZ", "aux", "xx", true, true], style) + +annotation-row(["looking", "look", "VERB", "VBG", "ROOT", "xxxx", true, false], style) + +annotation-row(["at", "at", "ADP", "IN", "prep", "xx", true, true], style) + +annotation-row(["buying", "buy", "VERB", "VBG", "pcomp", "xxxx", true, false], style) + +annotation-row(["U.K.", "u.k.", "PROPN", "NNP", "compound", "X.X.", false, false], style) + +annotation-row(["startup", "startup", "NOUN", "NN", "dobj", "xxxx", true, false], style) + +annotation-row(["for", "for", "ADP", "IN", "prep", "xxx", true, true], style) + +annotation-row(["$", "$", "SYM", "$", "quantmod", "$", false, false], style) + +annotation-row(["1", "1", "NUM", "CD", "compound", "d", false, false], style) + +annotation-row(["billion", "billion", "NUM", "CD", "pobj", "xxxx", true, false], style) + ++aside("Tip: Understanding tags and labels") + | Most of the tags and labels look pretty abstract, and they vary between + | languages. #[code spacy.explain()] will show you a short description – + | for example, #[code spacy.explain("VBZ")] returns "verb, 3rd person + | singular present". + +p + | Using spaCy's built-in #[+a("/docs/usage/visualizers") displaCy visualizer], + | here's what our example sentence and its dependencies look like: + ++codepen("030d1e4dfa6256cad8fdd59e6aefecbe", 460) diff --git a/website/docs/usage/_spacy-101/_serialization.jade b/website/docs/usage/_spacy-101/_serialization.jade new file mode 100644 index 000000000..27804344e --- /dev/null +++ b/website/docs/usage/_spacy-101/_serialization.jade @@ -0,0 +1,64 @@ +//- 💫 DOCS > USAGE > SPACY 101 > SERIALIZATION + +p + | If you've been modifying the pipeline, vocabulary, vectors and entities, + | or made updates to the model, you'll eventually want to + | #[strong save your progress] – for example, everything that's in your + | #[code nlp] object. This means you'll have to translate its contents and + | structure into a format that can be saved, like a file or a byte string. + | This process is called serialization. spaCy comes with + | #[strong built-in serialization methods] and supports the + | #[+a("http://www.diveintopython3.net/serializing.html#dump") Pickle protocol]. + ++aside("What's pickle?") + | Pickle is Python's built-in object persistance system. It lets you + | transfer arbitrary Python objects between processes. This is usually used + | to load an object to and from disk, but it's also used for distributed + | computing, e.g. with + | #[+a("https://spark.apache.org/docs/0.9.0/python-programming-guide.html") PySpark] + | or #[+a("http://dask.pydata.org/en/latest/") Dask]. When you unpickle an + | object, you're agreeing to execute whatever code it contains. It's like + | calling #[code eval()] on a string – so don't unpickle objects from + | untrusted sources. + +p + | All container classes, i.e. #[+api("language") #[code Language]], + | #[+api("doc") #[code Doc]], #[+api("vocab") #[code Vocab]] and + | #[+api("stringstore") #[code StringStore]] have the following methods + | available: + ++table(["Method", "Returns", "Example"]) + - style = [1, 0, 1] + +annotation-row(["to_bytes", "bytes", "nlp.to_bytes()"], style) + +annotation-row(["from_bytes", "object", "nlp.from_bytes(bytes)"], style) + +annotation-row(["to_disk", "-", "nlp.to_disk('/path')"], style) + +annotation-row(["from_disk", "object", "nlp.from_disk('/path')"], style) + +p + | For example, if you've processed a very large document, you can use + | #[+api("doc#to_disk") #[code Doc.to_disk]] to save it to a file on your + | local machine. This will save the document and its tokens, as well as + | the vocabulary associated with the #[code Doc]. + ++aside("Why saving the vocab?") + | Saving the vocabulary with the #[code Doc] is important, because the + | #[code Vocab] holds the context-independent information about the words, + | tags and labels, and their #[strong hash values]. If the #[code Vocab] + | wasn't saved with the #[code Doc], spaCy wouldn't know how to resolve + | those IDs back to strings. + ++code. + moby_dick = open('moby_dick.txt', 'r') # open a large document + doc = nlp(moby_dick) # process it + doc.to_disk('/moby_dick.bin') # save the processed Doc + +p + | If you need it again later, you can load it back into an empty #[code Doc] + | with an empty #[code Vocab] by calling + | #[+api("doc#from_disk") #[code from_disk()]]: + ++code. + from spacy.tokens import Doc # to create empty Doc + from spacy.vocab import Vocab # to create empty Vocab + + doc = Doc(Vocab()).from_disk('/moby_dick.bin') # load processed Doc diff --git a/website/docs/usage/_spacy-101/_similarity.jade b/website/docs/usage/_spacy-101/_similarity.jade new file mode 100644 index 000000000..e8ce692f0 --- /dev/null +++ b/website/docs/usage/_spacy-101/_similarity.jade @@ -0,0 +1,44 @@ +//- 💫 DOCS > USAGE > SPACY 101 > SIMILARITY + +p + | spaCy is able to compare two objects, and make a prediction of + | #[strong how similar they are]. Predicting similarity is useful for + | building recommendation systems or flagging duplicates. For example, you + | can suggest a user content that's similar to what they're currently + | looking at, or label a support ticket as a duplicate if it's very + | similar to an already existing one. + +p + | Each #[code Doc], #[code Span] and #[code Token] comes with a + | #[+api("token#similarity") #[code .similarity()]] method that lets you + | compare it with another object, and determine the similarity. Of course + | similarity is always subjective – whether "dog" and "cat" are similar + | really depends on how you're looking at it. spaCy's similarity model + | usually assumes a pretty general-purpose definition of similarity. + ++code. + tokens = nlp(u'dog cat banana') + + for token1 in tokens: + for token2 in tokens: + print(token1.similarity(token2)) + ++aside + | #[strong #[+procon("neutral", 16)] similarity:] identical#[br] + | #[strong #[+procon("pro", 16)] similarity:] similar (higher is more similar) #[br] + | #[strong #[+procon("con", 16)] similarity:] dissimilar (lower is less similar) + ++table(["", "dog", "cat", "banana"]) + each cells, label in {"dog": [1, 0.8, 0.24], "cat": [0.8, 1, 0.28], "banana": [0.24, 0.28, 1]} + +row + +cell.u-text-label.u-color-theme=label + for cell in cells + +cell.u-text-center #[code=cell.toFixed(2)] + | #[+procon(cell < 0.5 ? "con" : cell != 1 ? "pro" : "neutral")] + +p + | In this case, the model's predictions are pretty on point. A dog is very + | similar to a cat, whereas a banana is not very similar to either of them. + | Identical tokens are obviously 100% similar to each other (just not always + | exactly #[code 1.0], because of vector math and floating point + | imprecisions). diff --git a/website/docs/usage/_spacy-101/_tokenization.jade b/website/docs/usage/_spacy-101/_tokenization.jade new file mode 100644 index 000000000..d6911387c --- /dev/null +++ b/website/docs/usage/_spacy-101/_tokenization.jade @@ -0,0 +1,62 @@ +//- 💫 DOCS > USAGE > SPACY 101 > TOKENIZATION + +p + | During processing, spaCy first #[strong tokenizes] the text, i.e. + | segments it into words, punctuation and so on. This is done by applying + | rules specific to each language. For example, punctuation at the end of a + | sentence should be split off – whereas "U.K." should remain one token. + | Each #[code Doc] consists of individual tokens, and we can simply iterate + | over them: + ++code. + for token in doc: + print(token.text) + ++table([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).u-text-center + +row + for cell in ["Apple", "is", "looking", "at", "buying", "U.K.", "startup", "for", "$", "1", "billion"] + +cell=cell + +p + | First, the raw text is split on whitespace characters, similar to + | #[code text.split(' ')]. Then, the tokenizer processes the text from + | left to right. On each substring, it performs two checks: + ++list("numbers") + +item + | #[strong Does the substring match a tokenizer exception rule?] For + | example, "don't" does not contain whitespace, but should be split + | into two tokens, "do" and "n't", while "U.K." should always + | remain one token. + +item + | #[strong Can a prefix, suffix or infix be split off?] For example + | punctuation like commas, periods, hyphens or quotes. + +p + | If there's a match, the rule is applied and the tokenizer continues its + | loop, starting with the newly split substrings. This way, spaCy can split + | #[strong complex, nested tokens] like combinations of abbreviations and + | multiple punctuation marks. + ++aside + | #[strong Tokenizer exception:] Special-case rule to split a string into + | several tokens or prevent a token from being split when punctuation rules + | are applied.#[br] + | #[strong Prefix:] Character(s) at the beginning, e.g. + | #[code $], #[code (], #[code “], #[code ¿].#[br] + | #[strong Suffix:] Character(s) at the end, e.g. + | #[code km], #[code )], #[code ”], #[code !].#[br] + | #[strong Infix:] Character(s) in between, e.g. + | #[code -], #[code --], #[code /], #[code …].#[br] + ++image + include ../../../assets/img/docs/tokenization.svg + .u-text-right + +button("/assets/img/docs/tokenization.svg", false, "secondary").u-text-tag View large graphic + +p + | While punctuation rules are usually pretty general, tokenizer exceptions + | strongly depend on the specifics of the individual language. This is + | why each #[+a("/docs/api/language-models") available language] has its + | own subclass like #[code English] or #[code German], that loads in lists + | of hard-coded data and exception rules. diff --git a/website/docs/usage/_spacy-101/_training.jade b/website/docs/usage/_spacy-101/_training.jade new file mode 100644 index 000000000..9b283c0eb --- /dev/null +++ b/website/docs/usage/_spacy-101/_training.jade @@ -0,0 +1,52 @@ +//- 💫 DOCS > USAGE > SPACY 101 > TRAINING + +p + | spaCy's models are #[strong statistical] and every "decision" they make – + | for example, which part-of-speech tag to assign, or whether a word is a + | named entity – is a #[strong prediction]. This prediction is based + | on the examples the model has seen during #[strong training]. To train + | a model, you first need training data – examples of text, and the + | labels you want the model to predict. This could be a part-of-speech tag, + | a named entity or any other information. + +p + | The model is then shown the unlabelled text and will make a prediction. + | Because we know the correct answer, we can give the model feedback on its + | prediction in the form of an #[strong error gradient] of the + | #[strong loss function] that calculates the difference between the training + | example and the expected output. The greater the difference, the more + | significant the gradient and the updates to our model. + ++aside + | #[strong Training data:] Examples and their annotations.#[br] + | #[strong Text:] The input text the model should predict a label for.#[br] + | #[strong Label:] The label the model should predict.#[br] + | #[strong Gradient:] Gradient of the loss function calculating the + | difference between input and expected output. + ++image + include ../../../assets/img/docs/training.svg + .u-text-right + +button("/assets/img/docs/training.svg", false, "secondary").u-text-tag View large graphic + +p + | When training a model, we don't just want it to memorise our examples – + | we want it to come up with theory that can be + | #[strong generalised across other examples]. After all, we don't just want + | the model to learn that this one instance of "Amazon" right here is a + | company – we want it to learn that "Amazon", in contexts #[em like this], + | is most likely a company. That's why the training data should always be + | representative of the data we want to process. A model trained on + | Wikipedia, where sentences in the first person are extremely rare, will + | likely perform badly on Twitter. Similarly, a model trained on romantic + | novels will likely perform badly on legal text. + +p + | This also means that in order to know how the model is performing, + | and whether it's learning the right things, you don't only need + | #[strong training data] – you'll also need #[strong evaluation data]. If + | you only test the model with the data it was trained on, you'll have no + | idea how well it's generalising. If you want to train a model from scratch, + | you usually need at least a few hundred examples for both training and + | evaluation. To update an existing model, you can already achieve decent + | results with very few examples – as long as they're representative. diff --git a/website/docs/usage/_spacy-101/_vocab.jade b/website/docs/usage/_spacy-101/_vocab.jade new file mode 100644 index 000000000..cff0b106e --- /dev/null +++ b/website/docs/usage/_spacy-101/_vocab.jade @@ -0,0 +1,115 @@ +//- 💫 DOCS > USAGE > SPACY 101 > VOCAB & STRINGSTORE + +p + | Whenever possible, spaCy tries to store data in a vocabulary, the + | #[+api("vocab") #[code Vocab]], that will be + | #[strong shared by multiple documents]. To save memory, spaCy also + | encodes all strings to #[strong hash values] – in this case for example, + | "coffee" has the hash #[code 3197928453018144401]. Entity labels like + | "ORG" and part-of-speech tags like "VERB" are also encoded. Internally, + | spaCy only "speaks" in hash values. + ++aside + | #[strong Token]: A word, punctuation mark etc. #[em in context], including + | its attributes, tags and dependencies.#[br] + | #[strong Lexeme]: A "word type" with no context. Includes the word shape + | and flags, e.g. if it's lowercase, a digit or punctuation.#[br] + | #[strong Doc]: A processed container of tokens in context.#[br] + | #[strong Vocab]: The collection of lexemes.#[br] + | #[strong StringStore]: The dictionary mapping hash values to strings, for + | example #[code 3197928453018144401] → "coffee". + ++image + include ../../../assets/img/docs/vocab_stringstore.svg + .u-text-right + +button("/assets/img/docs/vocab_stringstore.svg", false, "secondary").u-text-tag View large graphic + +p + | If you process lots of documents containing the word "coffee" in all + | kinds of different contexts, storing the exact string "coffee" every time + | would take up way too much space. So instead, spaCy hashes the string + | and stores it in the #[+api("stringstore") #[code StringStore]]. You can + | think of the #[code StringStore] as a + | #[strong lookup table that works in both directions] – you can look up a + | string to get its hash, or a hash to get its string: + ++code. + doc = nlp(u'I like coffee') + assert doc.vocab.strings[u'coffee'] == 3197928453018144401 + assert doc.vocab.strings[3197928453018144401] == u'coffee' + ++aside("What does 'L' at the end of a hash mean?") + | If you return a hash value in the #[strong Python 2 interpreter], it'll + | show up as #[code 3197928453018144401L]. The #[code L] just means "long + | integer" – it's #[strong not] actually a part of the hash value. + +p + | Now that all strings are encoded, the entries in the vocabulary + | #[strong don't need to include the word text] themselves. Instead, + | they can look it up in the #[code StringStore] via its hash value. Each + | entry in the vocabulary, also called #[+api("lexeme") #[code Lexeme]], + | contains the #[strong context-independent] information about a word. + | For example, no matter if "love" is used as a verb or a noun in some + | context, its spelling and whether it consists of alphabetic characters + | won't ever change. Its hash value will also always be the same. + ++code. + for word in doc: + lexeme = doc.vocab[word.text] + print(lexeme.text, lexeme.orth, lexeme.shape_, lexeme.prefix_, lexeme.suffix_, + lexeme.is_alpha, lexeme.is_digit, lexeme.is_title, lexeme.lang_) + ++aside + | #[strong Text]: The original text of the lexeme.#[br] + | #[strong Orth]: The hash value of the lexeme.#[br] + | #[strong Shape]: The abstract word shape of the lexeme.#[br] + | #[strong Prefix]: By default, the first letter of the word string.#[br] + | #[strong Suffix]: By default, the last three letters of the word string.#[br] + | #[strong is alpha]: Does the lexeme consist of alphabetic characters?#[br] + | #[strong is digit]: Does the lexeme consist of digits?#[br] + ++table(["text", "orth", "shape", "prefix", "suffix", "is_alpha", "is_digit"]) + - var style = [0, 1, 1, 0, 0, 1, 1] + +annotation-row(["I", "4690420944186131903", "X", "I", "I", true, false], style) + +annotation-row(["love", "3702023516439754181", "xxxx", "l", "ove", true, false], style) + +annotation-row(["coffee", "3197928453018144401", "xxxx", "c", "ffe", true, false], style) + +p + | The mapping of words to hashes doesn't depend on any state. To make sure + | each value is unique, spaCy uses a + | #[+a("https://en.wikipedia.org/wiki/Hash_function") hash function] to + | calculate the hash #[strong based on the word string]. This also means + | that the hash for "coffee" will always be the same, no matter which model + | you're using or how you've configured spaCy. + +p + | However, hashes #[strong cannot be reversed] and there's no way to + | resolve #[code 3197928453018144401] back to "coffee". All spaCy can do + | is look it up in the vocabulary. That's why you always need to make + | sure all objects you create have access to the same vocabulary. If they + | don't, spaCy might not be able to find the strings it needs. + ++code. + from spacy.tokens import Doc + from spacy.vocab import Vocab + + doc = nlp(u'I like coffee') # original Doc + assert doc.vocab.strings[u'coffee'] == 3197928453018144401 # get hash + assert doc.vocab.strings[3197928453018144401] == u'coffee' # 👍 + + empty_doc = Doc(Vocab()) # new Doc with empty Vocab + # doc.vocab.strings[3197928453018144401] will raise an error :( + + empty_doc.vocab.strings.add(u'coffee') # add "coffee" and generate hash + assert doc.vocab.strings[3197928453018144401] == u'coffee' # 👍 + + new_doc = Doc(doc.vocab) # create new doc with first doc's vocab + assert doc.vocab.strings[3197928453018144401] == u'coffee' # 👍 + +p + | If the vocabulary doesn't contain a string for #[code 3197928453018144401], + | spaCy will raise an error. You can re-add "coffee" manually, but this + | only works if you actually #[em know] that the document contains that + | word. To prevent this problem, spaCy will also export the #[code Vocab] + | when you save a #[code Doc] or #[code nlp] object. This will give you + | the object and its encoded annotations, plus they "key" to decode it. diff --git a/website/docs/usage/_spacy-101/_word-vectors.jade b/website/docs/usage/_spacy-101/_word-vectors.jade new file mode 100644 index 000000000..cbb9d06f2 --- /dev/null +++ b/website/docs/usage/_spacy-101/_word-vectors.jade @@ -0,0 +1,152 @@ +//- 💫 DOCS > USAGE > SPACY 101 > WORD VECTORS + +p + | Similarity is determined by comparing #[strong word vectors] or "word + | embeddings", multi-dimensional meaning representations of a word. Word + | vectors can be generated using an algorithm like + | #[+a("https://en.wikipedia.org/wiki/Word2vec") word2vec]. Most of spaCy's + | #[+a("/docs/usage/models") default models] come with + | #[strong 300-dimensional vectors] that look like this: + ++code("banana.vector", false, false, 250). + array([2.02280000e-01, -7.66180009e-02, 3.70319992e-01, + 3.28450017e-02, -4.19569999e-01, 7.20689967e-02, + -3.74760002e-01, 5.74599989e-02, -1.24009997e-02, + 5.29489994e-01, -5.23800015e-01, -1.97710007e-01, + -3.41470003e-01, 5.33169985e-01, -2.53309999e-02, + 1.73800007e-01, 1.67720005e-01, 8.39839995e-01, + 5.51070012e-02, 1.05470002e-01, 3.78719985e-01, + 2.42750004e-01, 1.47449998e-02, 5.59509993e-01, + 1.25210002e-01, -6.75960004e-01, 3.58420014e-01, + -4.00279984e-02, 9.59490016e-02, -5.06900012e-01, + -8.53179991e-02, 1.79800004e-01, 3.38669986e-01, + 1.32300004e-01, 3.10209990e-01, 2.18779996e-01, + 1.68530002e-01, 1.98740005e-01, -5.73849976e-01, + -1.06490001e-01, 2.66689986e-01, 1.28380001e-01, + -1.28030002e-01, -1.32839993e-01, 1.26570001e-01, + 8.67229998e-01, 9.67210010e-02, 4.83060002e-01, + 2.12709993e-01, -5.49900010e-02, -8.24249983e-02, + 2.24079996e-01, 2.39749998e-01, -6.22599982e-02, + 6.21940017e-01, -5.98999977e-01, 4.32009995e-01, + 2.81430006e-01, 3.38420011e-02, -4.88150001e-01, + -2.13589996e-01, 2.74010003e-01, 2.40950003e-01, + 4.59500015e-01, -1.86049998e-01, -1.04970002e+00, + -9.73049998e-02, -1.89080000e-01, -7.09290028e-01, + 4.01950002e-01, -1.87680006e-01, 5.16870022e-01, + 1.25200003e-01, 8.41499984e-01, 1.20970003e-01, + 8.82389992e-02, -2.91959997e-02, 1.21510006e-03, + 5.68250008e-02, -2.74210006e-01, 2.55640000e-01, + 6.97930008e-02, -2.22580001e-01, -3.60060006e-01, + -2.24020004e-01, -5.36990017e-02, 1.20220006e+00, + 5.45350015e-01, -5.79980016e-01, 1.09049998e-01, + 4.21669990e-01, 2.06619993e-01, 1.29360005e-01, + -4.14570011e-02, -6.67770028e-01, 4.04670000e-01, + -1.52179999e-02, -2.76400000e-01, -1.56110004e-01, + -7.91980028e-02, 4.00369987e-02, -1.29439995e-01, + -2.40900001e-04, -2.67850012e-01, -3.81150007e-01, + -9.72450018e-01, 3.17259997e-01, -4.39509988e-01, + 4.19340014e-01, 1.83530003e-01, -1.52600005e-01, + -1.08080000e-01, -1.03579998e+00, 7.62170032e-02, + 1.65189996e-01, 2.65259994e-04, 1.66160002e-01, + -1.52810007e-01, 1.81229994e-01, 7.02740014e-01, + 5.79559989e-03, 5.16639985e-02, -5.97449988e-02, + -2.75510013e-01, -3.90489995e-01, 6.11319989e-02, + 5.54300010e-01, -8.79969969e-02, -4.16810006e-01, + 3.28260005e-01, -5.25489986e-01, -4.42880005e-01, + 8.21829960e-03, 2.44859993e-01, -2.29819998e-01, + -3.49810004e-01, 2.68940002e-01, 3.91660005e-01, + -4.19039994e-01, 1.61909997e-01, -2.62630010e+00, + 6.41340017e-01, 3.97430003e-01, -1.28680006e-01, + -3.19460005e-01, -2.56330013e-01, -1.22199997e-01, + 3.22750002e-01, -7.99330026e-02, -1.53479993e-01, + 3.15050006e-01, 3.05909991e-01, 2.60120004e-01, + 1.85530007e-01, -2.40429997e-01, 4.28860001e-02, + 4.06219989e-01, -2.42559999e-01, 6.38700008e-01, + 6.99829996e-01, -1.40430003e-01, 2.52090007e-01, + 4.89840001e-01, -6.10670000e-02, -3.67659986e-01, + -5.50890028e-01, -3.82649988e-01, -2.08430007e-01, + 2.28320003e-01, 5.12179971e-01, 2.78679997e-01, + 4.76520002e-01, 4.79510017e-02, -3.40079993e-01, + -3.28729987e-01, -4.19669986e-01, -7.54989982e-02, + -3.89539987e-01, -2.96219997e-02, -3.40700001e-01, + 2.21699998e-01, -6.28560036e-02, -5.19029975e-01, + -3.77739996e-01, -4.34770016e-03, -5.83010018e-01, + -8.75459984e-02, -2.39289999e-01, -2.47109994e-01, + -2.58870006e-01, -2.98940003e-01, 1.37150005e-01, + 2.98919994e-02, 3.65439989e-02, -4.96650010e-01, + -1.81600004e-01, 5.29389977e-01, 2.19919994e-01, + -4.45140004e-01, 3.77979994e-01, -5.70620000e-01, + -4.69460003e-02, 8.18059966e-02, 1.92789994e-02, + 3.32459986e-01, -1.46200001e-01, 1.71560004e-01, + 3.99809986e-01, 3.62170011e-01, 1.28160000e-01, + 3.16439986e-01, 3.75690013e-01, -7.46899992e-02, + -4.84800003e-02, -3.14009994e-01, -1.92860007e-01, + -3.12940001e-01, -1.75529998e-02, -1.75139993e-01, + -2.75870003e-02, -1.00000000e+00, 1.83870003e-01, + 8.14339995e-01, -1.89129993e-01, 5.09989977e-01, + -9.19600017e-03, -1.92950002e-03, 2.81890005e-01, + 2.72470005e-02, 4.34089988e-01, -5.49669981e-01, + -9.74259973e-02, -2.45399997e-01, -1.72030002e-01, + -8.86500031e-02, -3.02980006e-01, -1.35910004e-01, + -2.77649999e-01, 3.12860007e-03, 2.05559999e-01, + -1.57720000e-01, -5.23079991e-01, -6.47010028e-01, + -3.70139986e-01, 6.93930015e-02, 1.14009999e-01, + 2.75940001e-01, -1.38750002e-01, -2.72680014e-01, + 6.68910027e-01, -5.64539991e-02, 2.40170002e-01, + -2.67300010e-01, 2.98599988e-01, 1.00830004e-01, + 5.55920005e-01, 3.28489989e-01, 7.68579990e-02, + 1.55279994e-01, 2.56359994e-01, -1.07720003e-01, + -1.23590000e-01, 1.18270002e-01, -9.90289971e-02, + -3.43279988e-01, 1.15019999e-01, -3.78080010e-01, + -3.90120000e-02, -3.45930010e-01, -1.94040000e-01, + -3.35799992e-01, -6.23340011e-02, 2.89189994e-01, + 2.80319989e-01, -5.37410021e-01, 6.27939999e-01, + 5.69549985e-02, 6.21469975e-01, -2.52819985e-01, + 4.16700006e-01, -1.01079997e-02, -2.54339993e-01, + 4.00029987e-01, 4.24320012e-01, 2.26720005e-01, + 1.75530002e-01, 2.30489999e-01, 2.83230007e-01, + 1.38820007e-01, 3.12180002e-03, 1.70570001e-01, + 3.66849989e-01, 2.52470002e-03, -6.40089989e-01, + -2.97650009e-01, 7.89430022e-01, 3.31680000e-01, + -1.19659996e+00, -4.71559986e-02, 5.31750023e-01], dtype=float32) + +p + | The #[code .vector] attribute will return an object's vector. + | #[+api("doc#vector") #[code Doc.vector]] and + | #[+api("span#vector") #[code Span.vector]] will default to an average + | of their token vectors. You can also check if a token has a vector + | assigned, and get the L2 norm, which can be used to normalise + | vectors. + ++code. + tokens = nlp(u'dog cat banana sasquatch') + + for token in tokens: + print(token.text, token.has_vector, token.vector_norm, token.is_oov) + ++aside + | #[strong Text]: The original token text.#[br] + | #[strong has vector]: Does the token have a vector representation?#[br] + | #[strong Vector norm]: The L2 norm of the token's vector (the square root + | of the sum of the values squared)#[br] + | #[strong is OOV]: Is the word out-of-vocabulary? + ++table(["Text", "Has vector", "Vector norm", "OOV"]) + - var style = [0, 1, 1, 1] + +annotation-row(["dog", true, 7.033672992262838, false], style) + +annotation-row(["cat", true, 6.68081871208896, false], style) + +annotation-row(["banana", true, 6.700014292148571, false], style) + +annotation-row(["sasquatch", false, 0, true], style) + +p + | The words "dog", "cat" and "banana" are all pretty common in English, so + | they're part of the model's vocabulary, and come with a vector. The word + | "sasquatch" on the other hand is a lot less common and out-of-vocabulary + | – so its vector representation consists of 300 dimensions of #[code 0], + | which means it's practically nonexistent. + +p + | If your application will benefit from a large vocabulary with more + | vectors, you should consider using one of the + | #[+a("/docs/usage/models#available") larger models] instead of the default, + | smaller ones, which usually come with a clipped vocabulary. diff --git a/website/docs/usage/adding-languages.jade b/website/docs/usage/adding-languages.jade index 2d90028f0..a0b77ad17 100644 --- a/website/docs/usage/adding-languages.jade +++ b/website/docs/usage/adding-languages.jade @@ -3,32 +3,159 @@ include ../../_includes/_mixins p - | Adding full support for a language touches many different parts of the - | spaCy library. This guide explains how to fit everything together, and - | points you to the specific workflows for each component. Obviously, - | there are lots of ways you can organise your code when you implement - | your own #[+api("language") #[code Language]] class. This guide will - | focus on how it's done within spaCy. For full language support, we'll - | need to: + | Adding full support for a language touches many different parts of the + | spaCy library. This guide explains how to fit everything together, and + | points you to the specific workflows for each component. -+list("numbers") - +item - | Create a #[strong #[code Language] subclass]. - +item - | Define custom #[strong language data], like a stop list and tokenizer - | exceptions. - +item - | #[strong Test] the new language tokenizer. - +item - | #[strong Build the vocabulary], including word frequencies, Brown - | clusters and word vectors. - +item - | Set up a #[strong model direcory] and #[strong train] the tagger and - | parser. ++aside("Working on spaCy's source") + | To add a new language to spaCy, you'll need to + | #[strong modify the library's code]. The easiest way to do this is to + | clone the #[+src(gh("spaCy")) repository] and #[strong build spaCy from source]. + | For more information on this, see the #[+a("/docs/usage") installation guide]. + | Unlike spaCy's core, which is mostly written in Cython, all language + | data is stored in regular Python files. This means that you won't have to + | rebuild anything in between – you can simply make edits and reload spaCy + | to test them. + ++grid.o-no-block + +grid-col("half") + p + | Obviously, there are lots of ways you can organise your code when + | you implement your own language data. This guide will focus on + | how it's done within spaCy. For full language support, you'll + | need to create a #[code Language] subclass, define custom + | #[strong language data], like a stop list and tokenizer + | exceptions and test the new tokenizer. Once the language is set + | up, you can #[strong build the vocabulary], including word + | frequencies, Brown clusters and word vectors. Finally, you can + | #[strong train the tagger and parser], and save the model to a + | directory. + + p + | For some languages, you may also want to develop a solution for + | lemmatization and morphological analysis. + + +table-of-contents + +item #[+a("#101") Language data 101] + +item #[+a("#language-subclass") The Language subclass] + +item #[+a("#stop-words") Stop words] + +item #[+a("#tokenizer-exceptions") Tokenizer exceptions] + +item #[+a("#norm-exceptions") Norm exceptions] + +item #[+a("#lex-attrs") Lexical attributes] + +item #[+a("#syntax-iterators") Syntax iterators] + +item #[+a("#lemmatizer") Lemmatizer] + +item #[+a("#tag-map") Tag map] + +item #[+a("#morph-rules") Morph rules] + +item #[+a("#testing") Testing the tokenizer] + +item #[+a("#vocabulary") Building the vocabulary] + +item #[+a("#training") Training] + ++h(2, "101") Language data 101 + +include _spacy-101/_language-data p - | For some languages, you may also want to develop a solution for - | lemmatization and morphological analysis. + | The individual components #[strong expose variables] that can be imported + | within a language module, and added to the language's #[code Defaults]. + | Some components, like the punctuation rules, usually don't need much + | customisation and can simply be imported from the global rules. Others, + | like the tokenizer and norm exceptions, are very specific and will make + | a big difference to spaCy's performance on the particular language and + | training a language model. + + ++table(["Variable", "Type", "Description"]) + +row + +cell #[code STOP_WORDS] + +cell set + +cell Individual words. + + +row + +cell #[code TOKENIZER_EXCEPTIONS] + +cell dict + +cell Keyed by strings mapped to list of one dict per token with token attributes. + + +row + +cell #[code TOKEN_MATCH] + +cell regex + +cell Regexes to match complex tokens, e.g. URLs. + + +row + +cell #[code NORM_EXCEPTIONS] + +cell dict + +cell Keyed by strings, mapped to their norms. + + +row + +cell #[code TOKENIZER_PREFIXES] + +cell list + +cell Strings or regexes, usually not customised. + + +row + +cell #[code TOKENIZER_SUFFIXES] + +cell list + +cell Strings or regexes, usually not customised. + + +row + +cell #[code TOKENIZER_INFIXES] + +cell list + +cell Strings or regexes, usually not customised. + + +row + +cell #[code LEX_ATTRS] + +cell dict + +cell Attribute ID mapped to function. + + +row + +cell #[code SYNTAX_ITERATORS] + +cell dict + +cell + | Iterator ID mapped to function. Currently only supports + | #[code 'noun_chunks']. + + +row + +cell #[code LOOKUP] + +cell dict + +cell Keyed by strings mapping to their lemma. + + +row + +cell #[code LEMMA_RULES], #[code LEMMA_INDEX], #[code LEMMA_EXC] + +cell dict + +cell Lemmatization rules, keyed by part of speech. + + +row + +cell #[code TAG_MAP] + +cell dict + +cell + | Keyed by strings mapped to + | #[+a("http://universaldependencies.org/u/pos/all.html") Universal Dependencies] + | tags. + + +row + +cell #[code MORPH_RULES] + +cell dict + +cell Keyed by strings mapped to a dict of their morphological features. + ++aside("Should I ever update the global data?") + | Reuseable language data is collected as atomic pieces in the root of the + | #[+src(gh("spaCy", "lang")) spacy.lang] package. Often, when a new + | language is added, you'll find a pattern or symbol that's missing. Even + | if it isn't common in other languages, it might be best to add it to the + | shared language data, unless it has some conflicting interpretation. For + | instance, we don't expect to see guillemot quotation symbols + | (#[code »] and #[code «]) in English text. But if we do see + | them, we'd probably prefer the tokenizer to split them off. + ++infobox("For languages with non-latin characters") + | In order for the tokenizer to split suffixes, prefixes and infixes, spaCy + | needs to know the language's character set. If the language you're adding + | uses non-latin characters, you might need to add the required character + | classes to the global + | #[+src(gh("spacy", "spacy/lang/char_classes.py")) char_classes.py]. + | spaCy uses the #[+a("https://pypi.python.org/pypi/regex/") #[code regex] library] + | to keep this simple and readable. If the language requires very specific + | punctuation rules, you should consider overwriting the default regular + | expressions with your own in the language's #[code Defaults]. + +h(2, "language-subclass") Creating a #[code Language] subclass @@ -56,128 +183,36 @@ p from ...attrs import LANG from ...util import update_exc + # create Defaults class in the module scope (necessary for pickling!) + class XxxxxDefaults(Language.Defaults): + lex_attr_getters = dict(Language.Defaults.lex_attr_getters) + lex_attr_getters[LANG] = lambda text: 'xx' # language ISO code + + # optional: replace flags with custom functions, e.g. like_num() + lex_attr_getters.update(LEX_ATTRS) + + # merge base exceptions and custom tokenizer exceptions + tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) + stop_words = set(STOP_WORDS) + + # create actual Language class class Xxxxx(Language): lang = 'xx' # language ISO code - - # override defaults - class Defaults(Language.Defaults): - lex_attr_getters = dict(Language.Defaults.lex_attr_getters) - lex_attr_getters[LANG] = lambda text: 'xx' # language ISO code - - # optional: replace flags with custom functions, e.g. like_num() - lex_attr_getters.update(LEX_ATTRS) - - # merge base exceptions and custom tokenizer exceptions - tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) - stop_words = set(STOP_WORDS) + Defaults = XxxxxDefaults # override defaults # set default export – this allows the language class to be lazy-loaded __all__ = ['Xxxxx'] -+aside("Why lazy-loading?") ++infobox("Why lazy-loading?") | Some languages contain large volumes of custom data, like lemmatizer | loopup tables, or complex regular expression that are expensive to | compute. As of spaCy v2.0, #[code Language] classes are not imported on | initialisation and are only loaded when you import them directly, or load | a model that requires a language to be loaded. To lazy-load languages in - | your application, you can use the #[code util.get_lang_class()] helper + | your application, you can use the + | #[+api("util#get_lang_class") #[code util.get_lang_class()]] helper | function with the two-letter language code as its argument. -+h(2, "language-data") Adding language data - -p - | Every language is full of exceptions and special cases, especially - | amongst the most common words. Some of these exceptions are shared - | between multiple languages, while others are entirely idiosyncratic. - | spaCy makes it easy to deal with these exceptions on a case-by-case - | basis, by defining simple rules and exceptions. The exceptions data is - | defined in Python the - | #[+src(gh("spacy-dev-resources", "templates/new_language")) language data], - | so that Python functions can be used to help you generalise and combine - | the data as you require. - -p - | Here's an overview of the individual components that can be included - | in the language data. For more details on them, see the sections below. - -+image - include ../../assets/img/docs/language_data.svg - -+table(["File name", "Variables", "Description"]) - +row - +cell #[+src(gh("spacy-dev-resources", "templates/new_language/stop_words.py")) stop_words.py] - +cell #[code STOP_WORDS] (set) - +cell - | List of most common words. Matching tokens will return #[code True] - | for #[code is_stop]. - - +row - +cell #[+src(gh("spacy-dev-resources", "templates/new_language/tokenizer_exceptions.py")) tokenizer_exceptions.py] - +cell #[code TOKENIZER_EXCEPTIONS] (dict), #[code TOKEN_MATCH] (regex) - +cell - | Special-case rules for the tokenizer, for example, contractions - | and abbreviations containing punctuation. - - +row - +cell #[+src(gh("spaCy", "spacy/lang/punctuation.py")) punctuation.py] - +cell - | #[code TOKENIZER_PREFIXES], #[code TOKENIZER_SUFFIXES], - | #[code TOKENIZER_INFIXES] (dicts) - +cell Regular expressions for splitting tokens, e.g. on punctuation. - - +row - +cell #[+src(gh("spacy-dev-resources", "templates/new_language/lex_attrs.py")) lex_attrs.py] - +cell #[code LEX_ATTRS] (dict) - +cell - | Functions for setting lexical attributes on tokens, e.g. - | #[code is_punct] or #[code like_num]. - - +row - +cell #[+src(gh("spacy-dev-resources", "templates/new_language/lemmatizer.py")) lemmatizer.py] - +cell #[code LOOKUP] (dict) - +cell - | Lookup-based lemmatization table. If more lemmatizer data is - | available, it should live in #[code /lemmatizer/lookup.py]. - - +row - +cell /lemmatizer - +cell #[code LEMMA_RULES], #[code LEMMA_INDEX], #[code LEMMA_EXC] (dicts) - +cell Lemmatization rules, keyed by part of speech. - - +row - +cell #[+src(gh("spacy-dev-resources", "templates/new_language/tag_map.py")) tag_map.py] - +cell #[code TAG_MAP] (dict) - +cell - | Dictionary mapping strings in your tag set to - | #[+a("http://universaldependencies.org/u/pos/all.html") Universal Dependencies] - | tags. - - +row - +cell #[+src(gh()) morph_rules.py] - +cell #[code MORPH_RULES] (dict) - +cell Exception rules for morphological analysis of irregular words. - -+aside("Should I ever update the global data?") - | Reuseable language data is collected as atomic pieces in the root of the - | #[+src(gh("spaCy", "lang")) spacy.lang] package. Often, when a new - | language is added, you'll find a pattern or symbol that's missing. Even - | if it isn't common in other languages, it might be best to add it to the - | shared language data, unless it has some conflicting interpretation. For - | instance, we don't expect to see guillemot quotation symbols - | (#[code »] and #[code «]) in English text. But if we do see - | them, we'd probably prefer the tokenizer to split them off. - -+infobox("For languages with non-latin characters") - | In order for the tokenizer to split suffixes, prefixes and infixes, spaCy - | needs to know the language's character set. If the language you're adding - | uses non-latin characters, you might need to add the required character - | classes to the global - | #[+src(gh("spacy", "spacy/lang/char_classes.py")) char_classes.py]. - | spaCy uses the #[+a("https://pypi.python.org/pypi/regex/") #[code regex] library] - | to keep this simple and readable. If the language requires very specific - | punctuation rules, you should consider overwriting the default regular - | expressions with your own in the language's #[code Defaults]. - +h(3, "stop-words") Stop words p @@ -230,7 +265,7 @@ p TOKENIZER_EXCEPTIONS = { "don't": [ {ORTH: "do", LEMMA: "do"}, - {ORTH: "n't", LEMMA: "not", TAG: "RB"}] + {ORTH: "n't", LEMMA: "not", NORM: "not", TAG: "RB"}] } +infobox("Important note") @@ -280,14 +315,14 @@ p p | When adding the tokenizer exceptions to the #[code Defaults], you can use - | the #[code update_exc()] helper function to merge them with the global - | base exceptions (including one-letter abbreviations and emoticons). - | The function performs a basic check to make sure exceptions are - | provided in the correct format. It can take any number of exceptions - | dicts as its arguments, and will update and overwrite the exception in - | this order. For example, if your language's tokenizer exceptions include - | a custom tokenization pattern for "a.", it will overwrite the base - | exceptions with the language's custom one. + | the #[+api("util#update_exc") #[code update_exc()]] helper function to merge + | them with the global base exceptions (including one-letter abbreviations + | and emoticons). The function performs a basic check to make sure + | exceptions are provided in the correct format. It can take any number of + | exceptions dicts as its arguments, and will update and overwrite the + | exception in this order. For example, if your language's tokenizer + | exceptions include a custom tokenization pattern for "a.", it will + | overwrite the base exceptions with the language's custom one. +code("Example"). from ...util import update_exc @@ -298,13 +333,77 @@ p tokenizer_exceptions = update_exc(BASE_EXCEPTIONS, TOKENIZER_EXCEPTIONS) # {"a.": [{ORTH: "a.", LEMMA: "all"}], ":)": [{ORTH: ":)"}]} -//-+aside("About spaCy's custom pronoun lemma") ++infobox("About spaCy's custom pronoun lemma") | Unlike verbs and common nouns, there's no clear base form of a personal | pronoun. Should the lemma of "me" be "I", or should we normalize person | as well, giving "it" — or maybe "he"? spaCy's solution is to introduce a | novel symbol, #[code.u-nowrap -PRON-], which is used as the lemma for | all personal pronouns. ++h(3, "norm-exceptions") Norm exceptions + +p + | In addition to #[code ORTH] or #[code LEMMA], tokenizer exceptions can + | also set a #[code NORM] attribute. This is useful to specify a normalised + | version of the token – for example, the norm of "n't" is "not". By default, + | a token's norm equals its lowercase text. If the lowercase spelling of a + | word exists, norms should always be in lowercase. + ++aside-code("Norms vs. lemmas"). + doc = nlp(u"I'm gonna realise") + norms = [token.norm_ for token in doc] + lemmas = [token.lemma_ for token in doc] + assert norms == ['i', 'am', 'going', 'to', 'realize'] + assert lemmas == ['i', 'be', 'go', 'to', 'realise'] + +p + | spaCy usually tries to normalise words with different spellings to a single, + | common spelling. This has no effect on any other token attributes, or + | tokenization in general, but it ensures that + | #[strong equivalent tokens receive similar representations]. This can + | improve the model's predictions on words that weren't common in the + | training data, but are equivalent to other words – for example, "realize" + | and "realise", or "thx" and "thanks". + +p + | Similarly, spaCy also includes + | #[+src(gh("spaCy", "spacy/lang/norm_exceptions.py")) global base norms] + | for normalising different styles of quotation marks and currency + | symbols. Even though #[code $] and #[code €] are very different, spaCy + | normalises them both to #[code $]. This way, they'll always be seen as + | similar, no matter how common they were in the training data. + +p + | Norm exceptions can be provided as a simple dictionary. For more examples, + | see the English + | #[+src(gh("spaCy", "spacy/lang/en/norm_exceptions.py")) norm_exceptions.py]. + ++code("Example"). + NORM_EXCEPTIONS = { + "cos": "because", + "fav": "favorite", + "accessorise": "accessorize", + "accessorised": "accessorized" + } + +p + | To add the custom norm exceptions lookup table, you can use the + | #[code add_lookups()] helper functions. It takes the default attribute + | getter function as its first argument, plus a variable list of + | dictionaries. If a string's norm is found in one of the dictionaries, + | that value is used – otherwise, the default function is called and the + | token is assigned its default norm. + ++code. + lex_attr_getters[NORM] = add_lookups(Language.Defaults.lex_attr_getters[NORM], + NORM_EXCEPTIONS, BASE_NORMS) + +p + | The order of the dictionaries is also the lookup order – so if your + | language's norm exceptions overwrite any of the global exceptions, they + | should be added first. Also note that the tokenizer exceptions will + | always have priority over the atrribute getters. + +h(3, "lex-attrs") Lexical attributes p @@ -361,6 +460,33 @@ p | #[code lex_attr_getters.update(LEX_ATTRS)], only the new custom functions | are overwritten. ++h(3, "syntax-iterators") Syntax iterators + +p + | Syntax iterators are functions that compute views of a #[code Doc] + | object based on its syntax. At the moment, this data is only used for + | extracting + | #[+a("/docs/usage/dependency-parse#noun-chunks") noun chunks], which + | are available as the #[+api("doc#noun_chunks") #[code Doc.noun_chunks]] + | property. Because base noun phrases work differently across languages, + | the rules to compute them are part of the individual language's data. If + | a language does not include a noun chunks iterator, the property won't + | be available. For examples, see the existing syntax iterators: + ++aside-code("Noun chunks example"). + doc = nlp(u'A phrase with another phrase occurs.') + chunks = list(doc.noun_chunks) + assert chunks[0].text == "A phrase" + assert chunks[1].text == "another phrase" + ++table(["Language", "Source"]) + for lang, lang_id in {en: "English", de: "German", es: "Spanish"} + +row + +cell=lang + +cell + +src(gh("spaCy", "spacy/lang/" + lang_id + "/syntax_iterators.py")) + | lang/#{lang_id}/syntax_iterators.py + +h(3, "lemmatizer") Lemmatizer p @@ -382,8 +508,6 @@ p "ababábites": "ababábite" } -+aside("Where can I find lemmatizer data?") - p | To add a lookup lemmatizer to your language, import the #[code LOOKUP] | table and #[code Lemmatizer], and create a new classmethod: @@ -436,6 +560,8 @@ p +h(3, "morph-rules") Morph rules ++under-construction + +h(2, "testing") Testing the new language tokenizer p @@ -516,6 +642,8 @@ p +h(2, "vocabulary") Building the vocabulary ++under-construction + p | spaCy expects that common words will be cached in a | #[+api("vocab") #[code Vocab]] instance. The vocabulary caches lexical @@ -533,8 +661,8 @@ p | #[+src(gh("spacy-dev-resources", "training/word_freqs.py")) word_freqs.py] | script from the spaCy developer resources. Note that your corpus should | not be preprocessed (i.e. you need punctuation for example). The - | #[+a("/docs/usage/cli#model") #[code model] command] expects a - | tab-separated word frequencies file with three columns: + | #[+api("cli#model") #[code model]] command expects a tab-separated word + | frequencies file with three columns: +list("numbers") +item The number of times the word occurred in your language sample. @@ -609,6 +737,8 @@ p +h(3, "word-vectors") Training the word vectors ++under-construction + p | #[+a("https://en.wikipedia.org/wiki/Word2vec") Word2vec] and related | algorithms let you train useful word similarity models from unlabelled @@ -626,53 +756,37 @@ p | trains the model using #[+a("https://radimrehurek.com/gensim/") Gensim]. | The #[code vectors.bin] file should consist of one word and vector per line. -+h(2, "model-directory") Setting up a model directory - -p - | Once you've collected the word frequencies, Brown clusters and word - | vectors files, you can use the - | #[+a("/docs/usage/cli#model") #[code model] command] to create a data - | directory: - -+code(false, "bash"). - python -m spacy model [lang] [model_dir] [freqs_data] [clusters_data] [vectors_data] - -+aside-code("your_data_directory", "yaml"). +//-+aside-code("your_data_directory", "yaml"). ├── vocab/ - | ├── lexemes.bin # via nlp.vocab.dump(path) - | ├── strings.json # via nlp.vocab.strings.dump(file_) - | └── oov_prob # optional - ├── pos/ # optional - | ├── model # via nlp.tagger.model.dump(path) - | └── config.json # via Langage.train - ├── deps/ # optional - | ├── model # via nlp.parser.model.dump(path) - | └── config.json # via Langage.train - └── ner/ # optional - ├── model # via nlp.entity.model.dump(path) - └── config.json # via Langage.train - -p - | This creates a spaCy data directory with a vocabulary model, ready to be - | loaded. By default, the command expects to be able to find your language - | class using #[code spacy.util.get_lang_class(lang_id)]. - + | ├── lexemes.bin + | ├── strings.json + | └── oov_prob + ├── pos/ + | ├── model + | └── config.json + ├── deps/ + | ├── model + | └── config.json + └── ner/ + ├── model + └── config.json +h(2, "train-tagger-parser") Training the tagger and parser ++under-construction + p | You can now train the model using a corpus for your language annotated | with #[+a("http://universaldependencies.org/") Universal Dependencies]. | If your corpus uses the | #[+a("http://universaldependencies.org/docs/format.html") CoNLL-U] format, | i.e. files with the extension #[code .conllu], you can use the - | #[+a("/docs/usage/cli#convert") #[code convert] command] to convert it to - | spaCy's #[+a("/docs/api/annotation#json-input") JSON format] for training. + | #[+api("cli#convert") #[code convert]] command to convert it to spaCy's + | #[+a("/docs/api/annotation#json-input") JSON format] for training. p | Once you have your UD corpus transformed into JSON, you can train your - | model use the using spaCy's - | #[+a("/docs/usage/cli#train") #[code train] command]: + | model use the using spaCy's #[+api("cli#train") #[code train]] command: +code(false, "bash"). - python -m spacy train [lang] [output_dir] [train_data] [dev_data] [--n_iter] [--parser_L1] [--no_tagger] [--no_parser] [--no_ner] + python -m spacy train [lang] [output_dir] [train_data] [dev_data] [--n-iter] [--n-sents] [--use-gpu] [--no-tagger] [--no-parser] [--no-entities] diff --git a/website/docs/usage/customizing-pipeline.jade b/website/docs/usage/customizing-pipeline.jade deleted file mode 100644 index a4846d02e..000000000 --- a/website/docs/usage/customizing-pipeline.jade +++ /dev/null @@ -1,38 +0,0 @@ -//- 💫 DOCS > USAGE > CUSTOMIZING THE PIPELINE - -include ../../_includes/_mixins - -p - | spaCy provides several linguistic annotation functions by default. Each - | function takes a Doc object, and modifies it in-place. The default - | pipeline is #[code [nlp.tagger, nlp.entity, nlp.parser]]. spaCy 1.0 - | introduced the ability to customise this pipeline with arbitrary - | functions. - -+code. - def arbitrary_fixup_rules(doc): - for token in doc: - if token.text == u'bill' and token.tag_ == u'NNP': - token.tag_ = u'NN' - - def custom_pipeline(nlp): - return (nlp.tagger, arbitrary_fixup_rules, nlp.parser, nlp.entity) - - nlp = spacy.load('en', create_pipeline=custom_pipeline) - -p - | The easiest way to customise the pipeline is to pass a - | #[code create_pipeline] callback to the #[code spacy.load()] function. - -p - | The callback you pass to #[code create_pipeline] should take a single - | argument, and return a sequence of callables. Each callable in the - | sequence should accept a #[code Doc] object and modify it in place. - -p - | Instead of passing a callback, you can also write to the - | #[code .pipeline] attribute directly. - -+code. - nlp = spacy.load('en') - nlp.pipeline = [nlp.tagger] diff --git a/website/docs/usage/customizing-tokenizer.jade b/website/docs/usage/customizing-tokenizer.jade index d43fb438f..f56ce9fb1 100644 --- a/website/docs/usage/customizing-tokenizer.jade +++ b/website/docs/usage/customizing-tokenizer.jade @@ -11,18 +11,50 @@ p | #[code spaces] booleans, which allow you to maintain alignment of the | tokens into the original string. -+aside("See Also") - | If you haven't read up on spaCy's #[+a("data-model") data model] yet, - | you should probably have a look. The main point to keep in mind is that - | spaCy's #[code Doc] doesn't copy or refer to the original string. The - | string is reconstructed from the tokens when required. ++h(2, "101") Tokenizer 101 +include _spacy-101/_tokenization + ++h(3, "101-data") Tokenizer data + +p + | #[strong Global] and #[strong language-specific] tokenizer data is + | supplied via the language data in #[+src(gh("spaCy", "spacy/lang")) spacy/lang]. + | The tokenizer exceptions define special cases like "don't" in English, + | which needs to be split into two tokens: #[code {ORTH: "do"}] and + | #[code {ORTH: "n't", LEMMA: "not"}]. The prefixes, suffixes and infixes + | mosty define punctuation rules – for example, when to split off periods + | (at the end of a sentence), and when to leave token containing periods + | intact (abbreviations like "U.S."). + ++image + include ../../assets/img/docs/language_data.svg + .u-text-right + +button("/assets/img/docs/language_data.svg", false, "secondary").u-text-tag View large graphic + ++infobox + | For more details on the language-specific data, see the + | usage guide on #[+a("/docs/usage/adding-languages") adding languages]. +h(2, "special-cases") Adding special case tokenization rules p | Most domains have at least some idiosyncracies that require custom - | tokenization rules. Here's how to add a special case rule to an existing + | tokenization rules. This could be very certain expressions, or + | abbreviations only used in this specific field. + ++aside("Language data vs. custom tokenization") + | Tokenization rules that are specific to one language, but can be + | #[strong generalised across that language] should ideally live in the + | language data in #[+src(gh("spaCy", "spacy/lang")) spacy/lang] – we + | always appreciate pull requests! Anything that's specific to a domain or + | text type – like financial trading abbreviations, or Bavarian youth slang + | – should be added as a special case rule to your tokenizer instance. If + | you're dealing with a lot of customisations, it might make sense to create + | an entirely custom subclass. + +p + | Here's how to add a special case rule to an existing | #[+api("tokenizer") #[code Tokenizer]] instance: +code. @@ -30,15 +62,12 @@ p from spacy.symbols import ORTH, LEMMA, POS nlp = spacy.load('en') - assert [w.text for w in nlp(u'gimme that')] == [u'gimme', u'that'] - nlp.tokenizer.add_special_case(u'gimme', - [ - { - ORTH: u'gim', - LEMMA: u'give', - POS: u'VERB'}, - { - ORTH: u'me'}]) + doc = nlp(u'gimme that') # phrase to tokenize + assert [w.text for w in doc] == [u'gimme', u'that'] # current tokenization + + # add special case rule + special_case = [{ORTH: u'gim', LEMMA: u'give', POS: u'VERB'}, {ORTH: u'me'}] + nlp.tokenizer.add_special_case(u'gimme', special_case) assert [w.text for w in nlp(u'gimme that')] == [u'gim', u'me', u'that'] assert [w.lemma_ for w in nlp(u'gimme that')] == [u'give', u'me', u'that'] @@ -55,9 +84,8 @@ p | The special case rules have precedence over the punctuation splitting: +code. - nlp.tokenizer.add_special_case(u'...gimme...?', - [{ - ORTH: u'...gimme...?', LEMMA: u'give', TAG: u'VB'}]) + special_case = [{ORTH: u'...gimme...?', LEMMA: u'give', TAG: u'VB'}] + nlp.tokenizer.add_special_case(u'...gimme...?', special_case) assert len(nlp(u'...gimme...?')) == 1 p @@ -113,7 +141,7 @@ p else: tokens.append(substring) substring = '' - tokens.extend(suffixes) + tokens.extend(reversed(suffixes)) return tokens p @@ -137,8 +165,8 @@ p +h(2, "native-tokenizers") Customizing spaCy's Tokenizer class p - | Let's imagine you wanted to create a tokenizer for a new language. There - | are four things you would need to define: + | Let's imagine you wanted to create a tokenizer for a new language or + | specific domain. There are four things you would need to define: +list("numbers") +item @@ -170,14 +198,15 @@ p import re from spacy.tokenizer import Tokenizer - prefix_re = re.compile(r'''[\[\("']''') - suffix_re = re.compile(r'''[\]\)"']''') - def create_tokenizer(nlp): - return Tokenizer(nlp.vocab, - prefix_search=prefix_re.search, - suffix_search=suffix_re.search) + prefix_re = re.compile(r'''[\[\("']''') + suffix_re = re.compile(r'''[\]\)"']''') - nlp = spacy.load('en', tokenizer=create_make_doc) + def custom_tokenizer(nlp): + return Tokenizer(nlp.vocab, prefix_search=prefix_re.search, + suffix_search=suffix_re.search) + + nlp = spacy.load('en') + nlp.tokenizer = custom_tokenizer(nlp) p | If you need to subclass the tokenizer instead, the relevant methods to @@ -187,29 +216,68 @@ p +h(2, "custom-tokenizer") Hooking an arbitrary tokenizer into the pipeline p - | You can pass a custom tokenizer using the #[code make_doc] keyword, when - | you're creating the pipeline: + | The tokenizer is the first component of the processing pipeline and the + | only one that can't be replaced by writing to #[code nlp.pipeline]. This + | is because it has a different signature from all the other components: + | it takes a text and returns a #[code Doc], whereas all other components + | expect to already receive a tokenized #[code Doc]. -+code. - import spacy ++image + include ../../assets/img/docs/pipeline.svg + .u-text-right + +button("/assets/img/docs/pipeline.svg", false, "secondary").u-text-tag View large graphic - nlp = spacy.load('en', make_doc=my_tokenizer) p - | However, this approach often leaves us with a chicken-and-egg problem. - | To construct the tokenizer, we usually want attributes of the #[code nlp] - | pipeline. Specifically, we want the tokenizer to hold a reference to the - | pipeline's vocabulary object. Let's say we have the following class as - | our tokenizer: - + | To overwrite the existing tokenizer, you need to replace + | #[code nlp.tokenizer] with a custom function that takes a text, and + | returns a #[code Doc]. + ++code. + nlp = spacy.load('en') + nlp.tokenizer = my_tokenizer + ++table(["Argument", "Type", "Description"]) + +row + +cell #[code text] + +cell unicode + +cell The raw text to tokenize. + + +footrow + +cell returns + +cell #[code Doc] + +cell The tokenized document. + ++infobox("Important note: using a custom tokenizer") + .o-block + | In spaCy v1.x, you had to add a custom tokenizer by passing it to the + | #[code make_doc] keyword argument, or by passing a tokenizer "factory" + | to #[code create_make_doc]. This was unnecessarily complicated. Since + | spaCy v2.0, you can simply write to #[code nlp.tokenizer]. If your + | tokenizer needs the vocab, you can write a function and use + | #[code nlp.vocab]. + + +code-new. + nlp.tokenizer = my_tokenizer + nlp.tokenizer = my_tokenizer_factory(nlp.vocab) + +code-old. + nlp = spacy.load('en', make_doc=my_tokenizer) + nlp = spacy.load('en', create_make_doc=my_tokenizer_factory) + ++h(3, "custom-tokenizer-example") Example: A custom whitespace tokenizer + +p + | To construct the tokenizer, we usually want attributes of the #[code nlp] + | pipeline. Specifically, we want the tokenizer to hold a reference to the + | vocabulary object. Let's say we have the following class as + | our tokenizer: +code. - import spacy from spacy.tokens import Doc class WhitespaceTokenizer(object): - def __init__(self, nlp): - self.vocab = nlp.vocab + def __init__(self, vocab): + self.vocab = vocab def __call__(self, text): words = text.split(' ') @@ -218,28 +286,12 @@ p return Doc(self.vocab, words=words, spaces=spaces) p - | As you can see, we need a #[code vocab] instance to construct this — but - | we won't get the #[code vocab] instance until we get back the #[code nlp] - | object from #[code spacy.load()]. The simplest solution is to build the - | object in two steps: + | As you can see, we need a #[code Vocab] instance to construct this — but + | we won't have it until we get back the loaded #[code nlp] object. The + | simplest solution is to build the tokenizer in two steps. This also means + | that you can reuse the "tokenizer factory" and initialise it with + | different instances of #[code Vocab]. +code. nlp = spacy.load('en') - nlp.make_doc = WhitespaceTokenizer(nlp) - -p - | You can instead pass the class to the #[code create_make_doc] keyword, - | which is invoked as callback once the #[code nlp] object is ready: - -+code. - nlp = spacy.load('en', create_make_doc=WhitespaceTokenizer) - -p - | Finally, you can of course create your own subclasses, and create a bound - | #[code make_doc] method. The disadvantage of this approach is that spaCy - | uses inheritance to give each language-specific pipeline its own class. - | If you're working with multiple languages, a naive solution will - | therefore require one custom class per language you're working with. - | This might be at least annoying. You may be able to do something more - | generic by doing some clever magic with metaclasses or mixins, if that's - | the sort of thing you're into. + nlp.tokenizer = WhitespaceTokenizer(nlp.vocab) diff --git a/website/docs/usage/data-model.jade b/website/docs/usage/data-model.jade deleted file mode 100644 index 6be205178..000000000 --- a/website/docs/usage/data-model.jade +++ /dev/null @@ -1,264 +0,0 @@ -//- 💫 DOCS > USAGE > SPACY'S DATA MODEL - -include ../../_includes/_mixins - -p After reading this page, you should be able to: - -+list - +item Understand how spaCy's Doc, Span, Token and Lexeme object work - +item Start using spaCy's Cython API - +item Use spaCy more efficiently - -+h(2, "architecture") Architecture - -+image - include ../../assets/img/docs/architecture.svg - -+h(2, "design-considerations") Design considerations - -+h(3, "no-job-too-big") No job too big - -p - | When writing spaCy, one of my mottos was #[em no job too big]. I wanted - | to make sure that if Google or Facebook were founded tomorrow, spaCy - | would be the obvious choice for them. I wanted spaCy to be the obvious - | choice for web-scale NLP. This meant sweating about performance, because - | for web-scale tasks, Moore's law can't save you. - -p - | Most computational work gets less expensive over time. If you wrote a - | program to solve fluid dynamics in 2008, and you ran it again in 2014, - | you would expect it to be cheaper. For NLP, it often doesn't work out - | that way. The problem is that we're writing programs where the task is - | something like "Process all articles in the English Wikipedia". Sure, - | compute prices dropped from $0.80 per hour to $0.20 per hour on AWS in - | 2008-2014. But the size of Wikipedia grew from 3GB to 11GB. Maybe the - | job is a #[em little] cheaper in 2014 — but not by much. - -+h(3, "annotation-layers") Multiple layers of annotation - -p - | When I tell a certain sort of person that I'm a computational linguist, - | this comic is often the first thing that comes to their mind: - -+image("http://i.imgur.com/n3DTzqx.png", 450) - +image-caption © #[+a("http://xkcd.com") xkcd] - -p - | I've thought a lot about what this comic is really trying to say. It's - | probably not talking about #[em data models] — but in that sense at - | least, it really rings true. - -p - | You'll often need to model a document as a sequence of sentences. Other - | times you'll need to model it as a sequence of words. Sometimes you'll - | care about paragraphs, other times you won't. Sometimes you'll care - | about extracting quotes, which can cross paragraph boundaries. A quote - | can also occur within a sentence. When we consider sentence structure, - | things get even more complicated and contradictory. We have syntactic - | trees, sequences of entities, sequences of phrases, sub-word units, - | multi-word units... - -p - | Different applications are going to need to query different, - | overlapping, and often contradictory views of the document. They're - | often going to need to query them jointly. You need to be able to get - | the syntactic head of a named entity, or the sentiment of a paragraph. - -+h(2, "solutions") Solutions - -+h(3) Fat types, thin tokens - -+h(3) Static model, dynamic views - -p - | Different applications are going to need to query different, - | overlapping, and often contradictory views of the document. For this - | reason, I think it's a bad idea to have too much of the document - | structure reflected in the data model. If you structure the data - | according to the needs of one layer of annotation, you're going to need - | to copy the data and transform it in order to use a different layer of - | annotation. You'll soon have lots of copies, and no single source of - | truth. - -+h(3) Never go full stand-off - -+h(3) Implementation - -+h(3) Cython 101 - -+h(3) #[code cdef class Doc] - -p - | Let's start at the top. Here's the memory layout of the - | #[+api("doc") #[code Doc]] class, minus irrelevant details: - -+code. - from cymem.cymem cimport Pool - from ..vocab cimport Vocab - from ..structs cimport TokenC - - cdef class Doc: - cdef Pool mem - cdef Vocab vocab - - cdef TokenC* c - - cdef int length - cdef int max_length - -p - | So, our #[code Doc] class is a wrapper around a TokenC* array — that's - | where the actual document content is stored. Here's the #[code TokenC] - | struct, in its entirety: - -+h(3) #[code cdef struct TokenC] - -+code. - cdef struct TokenC: - const LexemeC* lex - uint64_t morph - univ_pos_t pos - bint spacy - int tag - int idx - int lemma - int sense - int head - int dep - bint sent_start - - uint32_t l_kids - uint32_t r_kids - uint32_t l_edge - uint32_t r_edge - - int ent_iob - int ent_type # TODO: Is there a better way to do this? Multiple sources of truth.. - hash_t ent_id - -p - | The token owns all of its linguistic annotations, and holds a const - | pointer to a #[code LexemeC] struct. The #[code LexemeC] struct owns all - | of the #[em vocabulary] data about the word — all the dictionary - | definition stuff that we want to be shared by all instances of the type. - | Here's the #[code LexemeC] struct, in its entirety: - -+h(3) #[code cdef struct LexemeC] - -+code. - cdef struct LexemeC: - - int32_t id - - int32_t orth # Allows the string to be retrieved - int32_t length # Length of the string - - uint64_t flags # These are the most useful parts. - int32_t cluster # Distributional similarity cluster - float prob # Probability - float sentiment # Slot for sentiment - - int32_t lang - - int32_t lower # These string views made sense - int32_t norm # when NLP meant linear models. - int32_t shape # Now they're less relevant, and - int32_t prefix # will probably be revised. - int32_t suffix - - float* vector # <-- This was a design mistake, and will change. - -+h(2, "dynamic-views") Dynamic views - -+h(3) Text - -p - | You might have noticed that in all of the structs above, there's not a - | string to be found. The strings are all stored separately, in the - | #[+api("stringstore") #[code StringStore]] class. The lexemes don't know - | the strings — they only know their integer IDs. The document string is - | never stored anywhere, either. Instead, it's reconstructed by iterating - | over the tokens, which look up the #[code orth] attribute of their - | underlying lexeme. Once we have the orth ID, we can fetch the string - | from the vocabulary. Finally, each token knows whether a single - | whitespace character (#[code ' ']) should be used to separate it from - | the subsequent tokens. This allows us to preserve whitespace. - -+code. - cdef print_text(Vocab vocab, const TokenC* tokens, int length): - for i in range(length): - word_string = vocab.strings[tokens.lex.orth] - if tokens.lex.spacy: - word_string += ' ' - print(word_string) - -p - | This is why you get whitespace tokens in spaCy — we need those tokens, - | so that we can reconstruct the document string. I also think you should - | have those tokens anyway. Most NLP libraries strip them, making it very - | difficult to recover the paragraph information once you're at the token - | level. You'll never have that sort of problem with spaCy — because - | there's a single source of truth. - -+h(3) #[code cdef class Token] - -p When you do... - -+code. - doc[i] - -p - | ...you get back an instance of class #[code spacy.tokens.token.Token]. - | This instance owns no data. Instead, it holds the information - | #[code (doc, i)], and uses these to retrieve all information via the - | parent container. - -+h(3) #[code cdef class Span] - -p When you do... - -+code. - doc[i : j] - -p - | ...you get back an instance of class #[code spacy.tokens.span.Span]. - | #[code Span] instances are also returned by the #[code .sents], - | #[code .ents] and #[code .noun_chunks] iterators of the #[code Doc] - | object. A #[code Span] is a slice of tokens, with an optional label - | attached. Its data model is: - -+code. - cdef class Span: - cdef readonly Doc doc - cdef int start - cdef int end - cdef int start_char - cdef int end_char - cdef int label - -p - | Once again, the #[code Span] owns almost no data. Instead, it refers - | back to the parent #[code Doc] container. - -p - | The #[code start] and #[code end] attributes refer to token positions, - | while #[code start_char] and #[code end_char] record the character - | positions of the span. By recording the character offsets, we can still - | use the #[code Span] object if the tokenization of the document changes. - -+h(3) #[code cdef class Lexeme] - -p When you do... - -+code. - vocab[u'the'] - -p - | ...you get back an instance of class #[code spacy.lexeme.Lexeme]. The - | #[code Lexeme]'s data model is: - -+code. - cdef class Lexeme: - cdef LexemeC* c - cdef readonly Vocab vocab diff --git a/website/docs/usage/deep-learning.jade b/website/docs/usage/deep-learning.jade index fec01b4ba..78448e43e 100644 --- a/website/docs/usage/deep-learning.jade +++ b/website/docs/usage/deep-learning.jade @@ -17,133 +17,10 @@ p | #[+a("http://deeplearning.net/software/theano/") Theano] is also | supported. -+code("Runtime usage"). - def count_entity_sentiment(nlp, texts): - '''Compute the net document sentiment for each entity in the texts.''' - entity_sentiments = collections.Counter(float) - for doc in nlp.pipe(texts, batch_size=1000, n_threads=4): - for ent in doc.ents: - entity_sentiments[ent.text] += doc.sentiment - return entity_sentiments - - def load_nlp(lstm_path, lang_id='en'): - def create_pipeline(nlp): - return [nlp.tagger, nlp.entity, SentimentAnalyser.load(lstm_path, nlp)] - return spacy.load(lang_id, create_pipeline=create_pipeline) ++under-construction p - | All you have to do is pass a #[code create_pipeline] callback function - | to #[code spacy.load()]. The function should take a - | #[code spacy.language.Language] object as its only argument, and return - | a sequence of callables. Each callable should accept a - | #[+api("docs") #[code Doc]] object, modify it in place, and return - | #[code None]. - -p - | Of course, operating on single documents is inefficient, especially for - | deep learning models. Usually we want to annotate many texts, and we - | want to process them in parallel. You should therefore ensure that your - | model component also supports a #[code .pipe()] method. The - | #[code .pipe()] method should be a well-behaved generator function that - | operates on arbitrarily large sequences. It should consume a small - | buffer of documents, work on them in parallel, and yield them one-by-one. - -+code("Custom Annotator Class"). - class SentimentAnalyser(object): - @classmethod - def load(cls, path, nlp): - with (path / 'config.json').open() as file_: - model = model_from_json(file_.read()) - with (path / 'model').open('rb') as file_: - lstm_weights = pickle.load(file_) - embeddings = get_embeddings(nlp.vocab) - model.set_weights([embeddings] + lstm_weights) - return cls(model) - - def __init__(self, model): - self._model = model - - def __call__(self, doc): - X = get_features([doc], self.max_length) - y = self._model.predict(X) - self.set_sentiment(doc, y) - - def pipe(self, docs, batch_size=1000, n_threads=2): - for minibatch in cytoolz.partition_all(batch_size, docs): - Xs = get_features(minibatch) - ys = self._model.predict(Xs) - for i, doc in enumerate(minibatch): - doc.sentiment = ys[i] - - def set_sentiment(self, doc, y): - doc.sentiment = float(y[0]) - # Sentiment has a native slot for a single float. - # For arbitrary data storage, there's: - # doc.user_data['my_data'] = y - - def get_features(docs, max_length): - Xs = numpy.zeros((len(docs), max_length), dtype='int32') - for i, doc in enumerate(minibatch): - for j, token in enumerate(doc[:max_length]): - Xs[i, j] = token.rank if token.has_vector else 0 - return Xs - -p - | By default, spaCy 1.0 downloads and uses the 300-dimensional - | #[+a("http://nlp.stanford.edu/projects/glove/") GloVe] common crawl - | vectors. It's also easy to replace these vectors with ones you've - | trained yourself, or to disable the word vectors entirely. If you've - | installed your word vectors into spaCy's #[+api("vocab") #[code Vocab]] - | object, here's how to use them in a Keras model: - -+code("Training with Keras"). - def train(train_texts, train_labels, dev_texts, dev_labels, - lstm_shape, lstm_settings, lstm_optimizer, batch_size=100, nb_epoch=5): - nlp = spacy.load('en', parser=False, tagger=False, entity=False) - embeddings = get_embeddings(nlp.vocab) - model = compile_lstm(embeddings, lstm_shape, lstm_settings) - train_X = get_features(nlp.pipe(train_texts)) - dev_X = get_features(nlp.pipe(dev_texts)) - model.fit(train_X, train_labels, validation_data=(dev_X, dev_labels), - nb_epoch=nb_epoch, batch_size=batch_size) - return model - - def compile_lstm(embeddings, shape, settings): - model = Sequential() - model.add( - Embedding( - embeddings.shape[1], - embeddings.shape[0], - input_length=shape['max_length'], - trainable=False, - weights=[embeddings] - ) - ) - model.add(Bidirectional(LSTM(shape['nr_hidden']))) - model.add(Dropout(settings['dropout'])) - model.add(Dense(shape['nr_class'], activation='sigmoid')) - model.compile(optimizer=Adam(lr=settings['lr']), loss='binary_crossentropy', - metrics=['accuracy']) - - return model - - def get_embeddings(vocab): - max_rank = max(lex.rank for lex in vocab if lex.has_vector) - vectors = numpy.ndarray((max_rank+1, vocab.vectors_length), dtype='float32') - for lex in vocab: - if lex.has_vector: - vectors[lex.rank] = lex.vector - return vectors - - def get_features(docs, max_length): - Xs = numpy.zeros(len(list(docs)), max_length, dtype='int32') - for i, doc in enumerate(docs): - for j, token in enumerate(doc[:max_length]): - Xs[i, j] = token.rank if token.has_vector else 0 - return Xs - -p - | For most applications, I recommend using pre-trained word embeddings + | For most applications, I it's recommended to use pre-trained word embeddings | without "fine-tuning". This means that you'll use the same embeddings | across different models, and avoid learning adjustments to them on your | training data. The embeddings table is large, and the values provided by @@ -153,7 +30,9 @@ p | adding another LSTM layer, using attention mechanism, using character | features, etc. -+h(2, "attribute-hooks") Attribute hooks (experimental) ++h(2, "attribute-hooks") Attribute hooks + ++under-construction p | Earlier, we saw how to store data in the new generic #[code user_data] diff --git a/website/docs/usage/dependency-parse.jade b/website/docs/usage/dependency-parse.jade index 904522bd4..beae36578 100644 --- a/website/docs/usage/dependency-parse.jade +++ b/website/docs/usage/dependency-parse.jade @@ -6,57 +6,85 @@ p | spaCy features a fast and accurate syntactic dependency parser, and has | a rich API for navigating the tree. The parser also powers the sentence | boundary detection, and lets you iterate over base noun phrases, or - | "chunks". + | "chunks". You can check whether a #[+api("doc") #[code Doc]] object has + | been parsed with the #[code doc.is_parsed] attribute, which returns a + | boolean value. If this attribute is #[code False], the default sentence + | iterator will raise an exception. -+aside-code("Example"). - import spacy ++h(2, "noun-chunks") Noun chunks + +tag-model("dependency parse") + +p + | Noun chunks are "base noun phrases" – flat phrases that have a noun as + | their head. You can think of noun chunks as a noun plus the words describing + | the noun – for example, "the lavish green grass" or "the world’s largest + | tech fund". To get the noun chunks in a document, simply iterate over + | #[+api("doc#noun_chunks") #[code Doc.noun_chunks]]. + ++code("Example"). nlp = spacy.load('en') - doc = nlp(u'I like green eggs and ham.') - for np in doc.noun_chunks: - print(np.text, np.root.text, np.root.dep_, np.root.head.text) - # I I nsubj like - # green eggs eggs dobj like - # ham ham conj eggs + doc = nlp(u'Autonomous cars shift insurance liability toward manufacturers') + for chunk in doc.noun_chunks: + print(chunk.text, chunk.root.text, chunk.root.dep_, + chunk.root.head.text) -p - | You can check whether a #[+api("doc") #[code Doc]] object has been - | parsed with the #[code doc.is_parsed] attribute, which returns a boolean - | value. If this attribute is #[code False], the default sentence iterator - | will raise an exception. ++aside + | #[strong Text:] The original noun chunk text.#[br] + | #[strong Root text:] The original text of the word connecting the noun + | chunk to the rest of the parse.#[br] + | #[strong Root dep:] Dependcy relation connecting the root to its head.#[br] + | #[strong Root head text:] The text of the root token's head.#[br] -+h(2, "displacy") The displaCy visualizer - -p - | The best way to understand spaCy's dependency parser is interactively, - | through the #[+a(DEMOS_URL + "/displacy", true) displaCy visualizer]. If - | you want to know how to write rules that hook into some type of syntactic - | construction, just plug the sentence into the visualizer and see how - | spaCy annotates it. ++table(["Text", "root.text", "root.dep_", "root.head.text"]) + - var style = [0, 0, 1, 0] + +annotation-row(["Autonomous cars", "cars", "nsubj", "shift"], style) + +annotation-row(["insurance liability", "liability", "dobj", "shift"], style) + +annotation-row(["manufacturers", "manufacturers", "pobj", "toward"], style) +h(2, "navigating") Navigating the parse tree p - | spaCy uses the terms #[em head] and #[em child] to describe the words - | connected by a single arc in the dependency tree. The term #[em dep] is - | used for the arc label, which describes the type of syntactic relation - | that connects the child to the head. As with other attributes, the value - | of #[code token.dep] is an integer. You can get the string value with - | #[code token.dep_]. + | spaCy uses the terms #[strong head] and #[strong child] to describe the words + | #[strong connected by a single arc] in the dependency tree. The term + | #[strong dep] is used for the arc label, which describes the type of + | syntactic relation that connects the child to the head. As with other + | attributes, the value of #[code .dep] is a hash value. You can get + | the string value with #[code .dep_]. -+aside-code("Example"). - from spacy.symbols import det - the, dog = nlp(u'the dog') - assert the.dep == det - assert the.dep_ == 'det' ++code("Example"). + doc = nlp(u'Autonomous cars shift insurance liability toward manufacturers') + for token in doc: + print(token.text, token.dep_, token.head.text, token.head.pos_, + [child for child in token.children]) + ++aside + | #[strong Text]: The original token text.#[br] + | #[strong Dep]: The syntactic relation connecting child to head.#[br] + | #[strong Head text]: The original text of the token head.#[br] + | #[strong Head POS]: The part-of-speech tag of the token head.#[br] + | #[strong Children]: The immediate syntactic dependents of the token. + ++table(["Text", "Dep", "Head text", "Head POS", "Children"]) + - var style = [0, 1, 0, 1, 0] + +annotation-row(["Autonomous", "amod", "cars", "NOUN", ""], style) + +annotation-row(["cars", "nsubj", "shift", "VERB", "Autonomous"], style) + +annotation-row(["shift", "ROOT", "shift", "VERB", "cars, liability"], style) + +annotation-row(["insurance", "compound", "liability", "NOUN", ""], style) + +annotation-row(["liability", "dobj", "shift", "VERB", "insurance, toward"], style) + +annotation-row(["toward", "prep", "liability", "NOUN", "manufacturers"], style) + +annotation-row(["manufacturers", "pobj", "toward", "ADP", ""], style) + ++codepen("dcf8d293367ca185b935ed2ca11ebedd", 370) p - | Because the syntactic relations form a tree, every word has exactly one - | head. You can therefore iterate over the arcs in the tree by iterating - | over the words in the sentence. This is usually the best way to match an - | arc of interest — from below: + | Because the syntactic relations form a tree, every word has + | #[strong exactly one head]. You can therefore iterate over the arcs in + | the tree by iterating over the words in the sentence. This is usually + | the best way to match an arc of interest — from below: +code. from spacy.symbols import nsubj, VERB + # Finding a verb with a subject from below — good verbs = set() for possible_subject in doc: @@ -82,6 +110,8 @@ p | attribute, which provides a sequence of #[+api("token") #[code Token]] | objects. ++h(3, "navigating-around") Iterating around the local tree + p | A few more convenience attributes are provided for iterating around the | local tree from the token. The #[code .lefts] and #[code .rights] @@ -90,75 +120,118 @@ p | two integer-typed attributes, #[code .n_rights] and #[code .n_lefts], | that give the number of left and right children. -+aside-code("Examples"). - apples = nlp(u'bright red apples on the tree')[2] - print([w.text for w in apples.lefts]) - # ['bright', 'red'] - print([w.text for w in apples.rights]) - # ['on'] - assert apples.n_lefts == 2 - assert apples.n_rights == 1 - - from spacy.symbols import nsubj - doc = nlp(u'Credit and mortgage account holders must submit their requests within 30 days.') - root = [w for w in doc if w.head is w][0] - subject = list(root.lefts)[0] - for descendant in subject.subtree: - assert subject.is_ancestor_of(descendant) - - from spacy.symbols import nsubj - doc = nlp(u'Credit and mortgage account holders must submit their requests.') - holders = doc[4] - span = doc[holders.left_edge.i : holders.right_edge.i + 1] - span.merge() - for word in doc: - print(word.text, word.pos_, word.dep_, word.head.text) - # Credit and mortgage account holders nsubj NOUN submit - # must VERB aux submit - # submit VERB ROOT submit - # their DET det requests - # requests NOUN dobj submit ++code. + doc = nlp(u'bright red apples on the tree') + assert [token.text for token in doc[2].lefts]) == [u'bright', u'red'] + assert [token.text for token in doc[2].rights]) == ['on'] + assert doc[2].n_lefts == 2 + assert doc[2].n_rights == 1 p | You can get a whole phrase by its syntactic head using the | #[code .subtree] attribute. This returns an ordered sequence of tokens. - | For the default English model, the parse tree is #[em projective], which - | means that there are no crossing brackets. The tokens returned by - | #[code .subtree] are therefore guaranteed to be contiguous. This is not - | true for the German model, which has many - | #[+a("https://explosion.ai/blog/german-model#word-order", true) non-projective dependencies]. | You can walk up the tree with the #[code .ancestors] attribute, and - | check dominance with the #[code .is_ancestor()] method. + | check dominance with the #[+api("token#is_ancestor") #[code .is_ancestor()]] + | method. + ++aside("Projective vs. non-projective") + | For the #[+a("/docs/usage/models#available") default English model], the + | parse tree is #[strong projective], which means that there are no crossing + | brackets. The tokens returned by #[code .subtree] are therefore guaranteed + | to be contiguous. This is not true for the German model, which has many + | #[+a(COMPANY_URL + "/blog/german-model#word-order", true) non-projective dependencies]. + ++code. + doc = nlp(u'Credit and mortgage account holders must submit their requests') + root = [token for token in doc if token.head is token][0] + subject = list(root.lefts)[0] + for descendant in subject.subtree: + assert subject.is_ancestor(descendant) + print(descendant.text, descendant.dep_, descendant.n_lefts, descendant.n_rights, + [ancestor.text for ancestor in descendant.ancestors]) + ++table(["Text", "Dep", "n_lefts", "n_rights", "ancestors"]) + - var style = [0, 1, 1, 1, 0] + +annotation-row(["Credit", "nmod", 0, 2, "holders, submit"], style) + +annotation-row(["and", "cc", 0, 0, "Credit, holders, submit"], style) + +annotation-row(["mortgage", "compound", 0, 0, "account, Credit, holders, submit"], style) + +annotation-row(["account", "conj", 1, 0, "Credit, holders, submit"], style) + +annotation-row(["holders", "nsubj", 1, 0, "submit"], style) p - | Finally, I often find the #[code .left_edge] and #[code right_edge] - | attributes especially useful. They give you the first and last token + | Finally, the #[code .left_edge] and #[code .right_edge] attributes + | can be especially useful, because they give you the first and last token | of the subtree. This is the easiest way to create a #[code Span] object - | for a syntactic phrase — a useful operation. + | for a syntactic phrase. Note that #[code .right_edge] gives a token + | #[strong within] the subtree — so if you use it as the end-point of a + | range, don't forget to #[code +1]! + ++code. + doc = nlp(u'Credit and mortgage account holders must submit their requests') + span = doc[doc[4].left_edge.i : doc[4].right_edge.i+1] + span.merge() + for token in doc: + print(token.text, token.pos_, token.dep_, token.head.text) + ++table(["Text", "POS", "Dep", "Head text"]) + - var style = [0, 1, 1, 0] + +annotation-row(["Credit and mortgage account holders", "NOUN", "nsubj", "submit"], style) + +annotation-row(["must", "VERB", "aux", "submit"], style) + +annotation-row(["submit", "VERB", "ROOT", "submit"], style) + +annotation-row(["their", "ADJ", "poss", "requests"], style) + +annotation-row(["requests", "NOUN", "dobj", "submit"], style) + ++h(2, "displacy") Visualizing dependencies p - | Note that #[code .right_edge] gives a token #[em within] the subtree — - | so if you use it as the end-point of a range, don't forget to #[code +1]! + | The best way to understand spaCy's dependency parser is interactively. + | To make this easier, spaCy v2.0+ comes with a visualization module. Simply + | pass a #[code Doc] or a list of #[code Doc] objects to + | displaCy and run #[+api("displacy#serve") #[code displacy.serve]] to + | run the web server, or #[+api("displacy#render") #[code displacy.render]] + | to generate the raw markup. If you want to know how to write rules that + | hook into some type of syntactic construction, just plug the sentence into + | the visualizer and see how spaCy annotates it. + ++code. + from spacy import displacy + + doc = nlp(u'Autonomous cars shift insurance liability toward manufacturers') + displacy.serve(doc, style='dep') + ++infobox + | For more details and examples, see the + | #[+a("/docs/usage/visualizers") usage guide on visualizing spaCy]. You + | can also test displaCy in our #[+a(DEMOS_URL + "/displacy", true) online demo]. +h(2, "disabling") Disabling the parser p - | The parser is loaded and enabled by default. If you don't need any of - | the syntactic information, you should disable the parser. Disabling the - | parser will make spaCy load and run much faster. Here's how to prevent - | the parser from being loaded: + | In the #[+a("/docs/usage/models/available") default models], the parser + | is loaded and enabled as part of the + | #[+a("docs/usage/language-processing-pipelines") standard processing pipeline]. + | If you don't need any of the syntactic information, you should disable + | the parser. Disabling the parser will make spaCy load and run much faster. + | If you want to load the parser, but need to disable it for specific + | documents, you can also control its use on the #[code nlp] object. +code. - import spacy + nlp = spacy.load('en', disable=['parser']) + nlp = English().from_disk('/model', disable=['parser']) + doc = nlp(u"I don't want parsed", disable=['parser']) - nlp = spacy.load('en', parser=False) - -p - | If you need to load the parser, but need to disable it for specific - | documents, you can control its use with the #[code parse] keyword - | argument: - -+code. - nlp = spacy.load('en') - doc1 = nlp(u'Text I do want parsed.') - doc2 = nlp(u"Text I don't want parsed", parse=False) ++infobox("Important note: disabling pipeline components") + .o-block + | Since spaCy v2.0 comes with better support for customising the + | processing pipeline components, the #[code parser] keyword argument + | has been replaced with #[code disable], which takes a list of + | #[+a("/docs/usage/language-processing-pipeline") pipeline component names]. + | This lets you disable both default and custom components when loading + | a model, or initialising a Language class via + | #[+api("language-from_disk") #[code from_disk]]. + +code-new. + nlp = spacy.load('en', disable=['parser']) + doc = nlp(u"I don't want parsed", disable=['parser']) + +code-old. + nlp = spacy.load('en', parser=False) + doc = nlp(u"I don't want parsed", parse=False) diff --git a/website/docs/usage/entity-recognition.jade b/website/docs/usage/entity-recognition.jade index 5f0dfc581..826de1543 100644 --- a/website/docs/usage/entity-recognition.jade +++ b/website/docs/usage/entity-recognition.jade @@ -9,73 +9,104 @@ p | locations, organizations and products. You can add arbitrary classes to | the entity recognition system, and update the model with new examples. -+aside-code("Example"). - import spacy - nlp = spacy.load('en') - doc = nlp(u'London is a big city in the United Kingdom.') - for ent in doc.ents: - print(ent.label_, ent.text) - # GPE London - # GPE United Kingdom ++h(2, "101") Named Entity Recognition 101 + +tag-model("named entities") + +include _spacy-101/_named-entities + ++h(2, "accessing") Accessing entity annotations p | The standard way to access entity annotations is the | #[+api("doc#ents") #[code doc.ents]] property, which produces a sequence | of #[+api("span") #[code Span]] objects. The entity type is accessible - | either as an integer ID or as a string, using the attributes + | either as a hash value or as a string, using the attributes | #[code ent.label] and #[code ent.label_]. The #[code Span] object acts | as a sequence of tokens, so you can iterate over the entity or index into | it. You can also get the text form of the whole entity, as though it were - | a single token. See the #[+api("span") API reference] for more details. + | a single token. p - | You can access token entity annotations using the #[code token.ent_iob] - | and #[code token.ent_type] attributes. The #[code token.ent_iob] - | attribute indicates whether an entity starts, continues or ends on the - | tag (In, Begin, Out). + | You can also access token entity annotations using the + | #[+api("token#attributes") #[code token.ent_iob]] and + | #[+api("token#attributes") #[code token.ent_type]] attributes. + | #[code token.ent_iob] indicates whether an entity starts, continues or + | ends on the tag. If no entity type is set on a token, it will return an + | empty string. + ++aside("IOB Scheme") + | #[code I] – Token is inside an entity.#[br] + | #[code O] – Token is outside an entity.#[br] + | #[code B] – Token is the beginning of an entity.#[br] +code("Example"). - doc = nlp(u'London is a big city in the United Kingdom.') - print(doc[0].text, doc[0].ent_iob, doc[0].ent_type_) - # (u'London', 2, u'GPE') - print(doc[1].text, doc[1].ent_iob, doc[1].ent_type_) - # (u'is', 3, u'') + doc = nlp(u'San Francisco considers banning sidewalk delivery robots') + + # document level + ents = [(e.text, e.start_char, e.end_char, e.label_) for e in doc.ents] + assert ents == [(u'San Francisco', 0, 13, u'GPE')] + + # token level + ent_san = [doc[0].text, doc[0].ent_iob_, doc[0].ent_type_] + ent_francisco = [doc[1].text, doc[1].ent_iob_, doc[1].ent_type_] + assert ent_san == [u'San', u'B', u'GPE'] + assert ent_francisco == [u'Francisco', u'I', u'GPE'] + ++table(["Text", "ent_iob", "ent_iob_", "ent_type_", "Description"]) + - var style = [0, 1, 1, 1, 0] + +annotation-row(["San", 3, "B", "GPE", "beginning of an entity"], style) + +annotation-row(["Francisco", 1, "I", "GPE", "inside an entity"], style) + +annotation-row(["considers", 2, "O", '""', "outside an entity"], style) + +annotation-row(["banning", 2, "O", '""', "outside an entity"], style) + +annotation-row(["sidewalk", 2, "O", '""', "outside an entity"], style) + +annotation-row(["delivery", 2, "O", '""', "outside an entity"], style) + +annotation-row(["robots", 2, "O", '""', "outside an entity"], style) +h(2, "setting") Setting entity annotations p | To ensure that the sequence of token annotations remains consistent, you - | have to set entity annotations at the document level — you can't write - | directly to the #[code token.ent_iob] or #[code token.ent_type] - | attributes. The easiest way to set entities is to assign to the - | #[code doc.ents] attribute. + | have to set entity annotations #[strong at the document level]. However, + | you can't write directly to the #[code token.ent_iob] or + | #[code token.ent_type] attributes, so the easiest way to set entities is + | to assign to the #[+api("doc#ents") #[code doc.ents]] attribute + | and create the new entity as a #[+api("span") #[code Span]]. +code("Example"). - doc = nlp(u'London is a big city in the United Kingdom.') - doc.ents = [] - assert doc[0].ent_type_ == '' - doc.ents = [Span(doc, 0, 1, label=doc.vocab.strings['GPE'])] - assert doc[0].ent_type_ == 'GPE' - doc.ents = [] - doc.ents = [(u'LondonCity', doc.vocab.strings['GPE'], 0, 1)] + from spacy.tokens import Span + + doc = nlp(u'Netflix is hiring a new VP of global policy') + # the model didn't recognise any entities :( + + ORG = doc.vocab.strings[u'ORG'] # get hash value of entity label + netflix_ent = Span(doc, 0, 1, label=ORG) # create a Span for the new entity + doc.ents = [netflix_ent] + + ents = [(e.text, e.start_char, e.end_char, e.label_) for e in doc.ents] + assert ents = [(u'Netflix', 0, 7, u'ORG')] p - | The value you assign should be a sequence, the values of which - | can either be #[code Span] objects, or #[code (ent_id, ent_type, start, end)] - | tuples, where #[code start] and #[code end] are token offsets that - | describe the slice of the document that should be annotated. + | Keep in mind that you need to create a #[code Span] with the start and + | end index of the #[strong token], not the start and end index of the + | entity in the document. In this case, "Netflix" is token #[code (0, 1)] – + | but at the document level, the entity will have the start and end + | indices #[code (0, 7)]. + ++h(3, "setting-from-array") Setting entity annotations from array p - | You can also assign entity annotations using the #[code doc.from_array()] - | method. To do this, you should include both the #[code ENT_TYPE] and the - | #[code ENT_IOB] attributes in the array you're importing from. + | You can also assign entity annotations using the + | #[+api("doc#from_array") #[code doc.from_array()]] method. To do this, + | you should include both the #[code ENT_TYPE] and the #[code ENT_IOB] + | attributes in the array you're importing from. -+code("Example"). - from spacy.attrs import ENT_IOB, ENT_TYPE ++code. import numpy + from spacy.attrs import ENT_IOB, ENT_TYPE doc = nlp.make_doc(u'London is a big city in the United Kingdom.') assert list(doc.ents) == [] + header = [ENT_IOB, ENT_TYPE] attr_array = numpy.zeros((len(doc), len(header))) attr_array[0, 0] = 2 # B @@ -83,12 +114,14 @@ p doc.from_array(header, attr_array) assert list(doc.ents)[0].text == u'London' ++h(3, "setting-cython") Setting entity annotations in Cython + p | Finally, you can always write to the underlying struct, if you compile - | a Cython function. This is easy to do, and allows you to write efficient - | native code. + | a #[+a("http://cython.org/") Cython] function. This is easy to do, and + | allows you to write efficient native code. -+code("Example"). ++code. # cython: infer_types=True from spacy.tokens.doc cimport Doc @@ -104,93 +137,46 @@ p | you'll have responsibility for ensuring that the data is left in a | consistent state. - -+h(2, "displacy") Visualizing named entities - -p - | The #[+a(DEMOS_URL + "/displacy-ent/") displaCy #[sup ENT] visualizer] - | lets you explore an entity recognition model's behaviour interactively. - | If you're training a model, it's very useful to run the visualization - | yourself. To help you do that, spaCy v2.0+ comes with a visualization - | module. Simply pass a #[code Doc] or a list of #[code Doc] objects to - | displaCy and run #[+api("displacy#serve") #[code displacy.serve]] to - | run the web server, or #[+api("displacy#render") #[code displacy.render]] - | to generate the raw markup. - -p - | For more details and examples, see the - | #[+a("/docs/usage/visualizers") usage workflow on visualizing spaCy]. - -+code("Named Entity example"). - import spacy - from spacy import displacy - - text = """But Google is starting from behind. The company made a late push - into hardware, and Apple’s Siri, available on iPhones, and Amazon’s Alexa - software, which runs on its Echo and Dot devices, have clear leads in - consumer adoption.""" - - nlp = spacy.load('custom_ner_model') - doc = nlp(text) - displacy.serve(doc, style='ent') - -+codepen("a73f8b68f9af3157855962b283b364e4", 345) - +h(2, "entity-types") Built-in entity types ++aside("Tip: Understanding entity types") + | You can also use #[code spacy.explain()] to get the description for the + | string representation of an entity label. For example, + | #[code spacy.explain("LANGUAGE")] will return "any named language". + include ../api/_annotation/_named-entities -+aside("Install") - | The #[+api("load") spacy.load()] function configures a pipeline that - | includes all of the available annotators for the given ID. In the example - | above, the #[code 'en'] ID tells spaCy to load the default English - | pipeline. If you have installed the data with - | #[code python -m spacy.en.download] this will include the entity - | recognition model. - +h(2, "updating") Training and updating ++under-construction + p | To provide training examples to the entity recogniser, you'll first need - | to create an instance of the #[code GoldParse] class. You can specify - | your annotations in a stand-off format or as token tags. - -+code. - import spacy - import random - from spacy.gold import GoldParse - from spacy.language import EntityRecognizer - - train_data = [ - ('Who is Chaka Khan?', [(7, 17, 'PERSON')]), - ('I like London and Berlin.', [(7, 13, 'LOC'), (18, 24, 'LOC')]) - ] - - nlp = spacy.load('en', entity=False, parser=False) - ner = EntityRecognizer(nlp.vocab, entity_types=['PERSON', 'LOC']) - - for itn in range(5): - random.shuffle(train_data) - for raw_text, entity_offsets in train_data: - doc = nlp.make_doc(raw_text) - gold = GoldParse(doc, entities=entity_offsets) - - nlp.tagger(doc) - ner.update(doc, gold) - ner.model.end_training() - -p + | to create an instance of the #[+api("goldparse") #[code GoldParse]] class. + | You can specify your annotations in a stand-off format or as token tags. | If a character offset in your entity annotations don't fall on a token | boundary, the #[code GoldParse] class will treat that annotation as a | missing value. This allows for more realistic training, because the | entity recogniser is allowed to learn from examples that may feature | tokenizer errors. -+aside-code("Example"). ++code. + train_data = [('Who is Chaka Khan?', [(7, 17, 'PERSON')]), + ('I like London and Berlin.', [(7, 13, 'LOC'), (18, 24, 'LOC')])] + ++code. doc = Doc(nlp.vocab, [u'rats', u'make', u'good', u'pets']) gold = GoldParse(doc, [u'U-ANIMAL', u'O', u'O', u'O']) - ner = EntityRecognizer(nlp.vocab, entity_types=['ANIMAL']) - ner.update(doc, gold) + ++infobox + | For more details on #[strong training and updating] the named entity + | recognizer, see the usage guides on #[+a("/docs/usage/training") training] + | and #[+a("/docs/usage/training-ner") training the named entity recognizer], + | or check out the runnable + | #[+src(gh("spaCy", "examples/training/train_ner.py")) training script] + | on GitHub. + ++h(3, "updating-biluo") The BILUO Scheme p | You can also provide token-level entity annotation, using the @@ -237,3 +223,34 @@ p | loss, via the #[+a("http://www.aclweb.org/anthology/C12-1059") dynamic oracle] | imitation learning strategy. The transition system is equivalent to the | BILOU tagging scheme. + ++h(2, "displacy") Visualizing named entities + +p + | The #[+a(DEMOS_URL + "/displacy-ent/") displaCy #[sup ENT] visualizer] + | lets you explore an entity recognition model's behaviour interactively. + | If you're training a model, it's very useful to run the visualization + | yourself. To help you do that, spaCy v2.0+ comes with a visualization + | module. Simply pass a #[code Doc] or a list of #[code Doc] objects to + | displaCy and run #[+api("displacy#serve") #[code displacy.serve]] to + | run the web server, or #[+api("displacy#render") #[code displacy.render]] + | to generate the raw markup. + +p + | For more details and examples, see the + | #[+a("/docs/usage/visualizers") usage guide on visualizing spaCy]. + ++code("Named Entity example"). + import spacy + from spacy import displacy + + text = """But Google is starting from behind. The company made a late push + into hardware, and Apple’s Siri, available on iPhones, and Amazon’s Alexa + software, which runs on its Echo and Dot devices, have clear leads in + consumer adoption.""" + + nlp = spacy.load('custom_ner_model') + doc = nlp(text) + displacy.serve(doc, style='ent') + ++codepen("a73f8b68f9af3157855962b283b364e4", 345) diff --git a/website/docs/usage/index.jade b/website/docs/usage/index.jade index da13f4d81..817b08ba9 100644 --- a/website/docs/usage/index.jade +++ b/website/docs/usage/index.jade @@ -21,10 +21,11 @@ p +qs({config: 'venv', os: 'linux'}) source .env/bin/activate +qs({config: 'venv', os: 'windows'}) .env\Scripts\activate - +qs({package: 'pip'}) pip install -U spacy + +qs({config: 'gpu', os: 'mac'}) export PATH=$PATH:/usr/local/cuda-8.0/bin + +qs({config: 'gpu', os: 'linux'}) export PATH=$PATH:/usr/local/cuda-8.0/bin - +qs({package: 'conda'}) conda config --add channels conda-forge - +qs({package: 'conda'}) conda install spacy + +qs({package: 'pip'}) pip install -U spacy + +qs({package: 'conda'}) conda install -c conda-forge spacy +qs({package: 'source'}) git clone https://github.com/explosion/spaCy +qs({package: 'source'}) cd spaCy @@ -34,6 +35,7 @@ p +qs({model: 'en'}) python -m spacy download en +qs({model: 'de'}) python -m spacy download de +qs({model: 'fr'}) python -m spacy download fr + +qs({model: 'es'}) python -m spacy download es +h(2, "installation") Installation instructions @@ -80,6 +82,27 @@ p | #[+a("https://github.com/conda-forge/spacy-feedstock") this repository]. | Improvements and pull requests to the recipe and setup are always appreciated. ++h(2, "gpu") Run spaCy with GPU + +p + | As of v2.0, spaCy's comes with neural network models that are implemented + | in our machine learning library, #[+a(gh("thinc")) Thinc]. For GPU + | support, we've been grateful to use the work of + | #[+a("http://chainer.org") Chainer]'s CuPy module, which provides + | a NumPy-compatible interface for GPU arrays. + +p + | First, install follows the normal CUDA installation procedure. Next, set + | your environment variables so that the installation will be able to find + | CUDA. Finally, install spaCy. + ++code(false, "bash"). + export CUDA_HOME=/usr/local/cuda-8.0 # Or wherever your CUDA is + export PATH=$PATH:$CUDA_HOME/bin + + pip install spacy + python -c "import thinc.neural.gpu_ops" # Check the GPU ops were built + +h(2, "source") Compile from source p @@ -175,6 +198,136 @@ p +cell Python 3.5+ +cell Visual Studio 2015 ++h(2, "troubleshooting") Troubleshooting guide + +p + | This section collects some of the most common errors you may come + | across when installing, loading and using spaCy, as well as their solutions. + ++aside("Help us improve this guide") + | Did you come across a problem like the ones listed here and want to + | share the solution? You can find the "Suggest edits" button at the + | bottom of this page that points you to the source. We always + | appreciate #[+a(gh("spaCy") + "/pulls") pull requests]! + ++h(3, "compatible-model") No compatible model found + ++code(false, "text"). + No compatible model found for [lang] (spaCy v#{SPACY_VERSION}). + +p + | This usually means that the model you're trying to download does not + | exist, or isn't available for your version of spaCy. Check the + | #[+a(gh("spacy-models", "compatibility.json")) compatibility table] + | to see which models are available for your spaCy version. If you're using + | an old version, consider upgrading to the latest release. Note that while + | spaCy supports tokenization for + | #[+a("/docs/api/language-models/#alpha-support") a variety of languages], + | not all of them come with statistical models. To only use the tokenizer, + | import the language's #[code Language] class instead, for example + | #[code from spacy.fr import French]. + ++h(3, "symlink-privilege") Symbolic link privilege not held + ++code(false, "text"). + OSError: symbolic link privilege not held + +p + | To create #[+a("/docs/usage/models/#usage") shortcut links] that let you + | load models by name, spaCy creates a symbolic link in the + | #[code spacy/data] directory. This means your user needs permission to do + | this. The above error mostly occurs when doing a system-wide installation, + | which will create the symlinks in a system directory. Run the + | #[code download] or #[code link] command as administrator, or use a + | #[code virtualenv] to install spaCy in a user directory, instead + | of doing a system-wide installation. + ++h(3, "no-cache-dir") No such option: --no-cache-dir + ++code(false, "text"). + no such option: --no-cache-dir + +p + | The #[code download] command uses pip to install the models and sets the + | #[code --no-cache-dir] flag to prevent it from requiring too much memory. + | #[+a("https://pip.pypa.io/en/stable/reference/pip_install/#caching") This setting] + | requires pip v6.0 or newer. Run #[code pip install -U pip] to upgrade to + | the latest version of pip. To see which version you have installed, + | run #[code pip --version]. + ++h(3, "import-error") Import error + ++code(false, "text"). + Import Error: No module named spacy + +p + | This error means that the spaCy module can't be located on your system, or in + | your environment. Make sure you have spaCy installed. If you're using a + | #[code virtualenv], make sure it's activated and check that spaCy is + | installed in that environment – otherwise, you're trying to load a system + | installation. You can also run #[code which python] to find out where + | your Python executable is located. + ++h(3, "import-error-models") Import error: models + ++code(false, "text"). + ImportError: No module named 'en_core_web_sm' + +p + | As of spaCy v1.7, all models can be installed as Python packages. This means + | that they'll become importable modules of your application. When creating + | #[+a("/docs/usage/models/#usage") shortcut links], spaCy will also try + | to import the model to load its meta data. If this fails, it's usually a + | sign that the package is not installed in the current environment. + | Run #[code pip list] or #[code pip freeze] to check which model packages + | you have installed, and install the + | #[+a("/docs/usage/models#available") correct models] if necessary. If you're + | importing a model manually at the top of a file, make sure to use the name + | of the package, not the shortcut link you've created. + ++h(3, "vocab-strings") File not found: vocab/strings.json + ++code(false, "text"). + FileNotFoundError: No such file or directory: [...]/vocab/strings.json + +p + | This error may occur when using #[code spacy.load()] to load + | a language model – either because you haven't set up a + | #[+a("/docs/usage/models/#usage") shortcut link] for it, or because it + | doesn't actually exist. Set up a + | #[+a("/docs/usage/models/#usage") shortcut link] for the model + | you want to load. This can either be an installed model package, or a + | local directory containing the model data. If you want to use one of the + | #[+a("/docs/api/language-models/#alpha-support") alpha tokenizers] for + | languages that don't yet have a statistical model, you should import its + | #[code Language] class instead, for example + | #[code from spacy.lang.bn import Bengali]. + ++h(3, "command-not-found") Command not found + ++code(false, "text"). + command not found: spacy + +p + | This error may occur when running the #[code spacy] command from the + | command line. spaCy does not currently add an entry to our #[code PATH] + | environment variable, as this can lead to unexpected results, especially + | when using #[code virtualenv]. Run the command with #[code python -m], + | for example #[code python -m spacy download en]. For more info on this, + | see #[+api("cli#download") download]. + ++h(3, "module-load") 'module' object has no attribute 'load' + ++code(false, "text"). + AttributeError: 'module' object has no attribute 'load' + +p + | While this could technically have many causes, including spaCy being + | broken, the most likely one is that your script's file or directory name + | is "shadowing" the module – e.g. your file is called #[code spacy.py], + | or a directory you're importing from is called #[code spacy]. So, when + | using spaCy, never call anything else #[code spacy]. + +h(2, "tests") Run tests p @@ -185,12 +338,14 @@ p python -c "import os; import spacy; print(os.path.dirname(spacy.__file__))" p - | Then run #[code pytest] on that directory. The flags #[code --vectors], - | #[code --slow] and #[code --model] are optional and enable additional - | tests: + | Then run #[code pytest] on that directory. The flags #[code --slow] and + | #[code --model] are optional and enable additional tests. +code(false, "bash"). # make sure you are using recent pytest version python -m pip install -U pytest - python -m pytest <spacy-directory> --vectors --models --slow + python -m pytest <spacy-directory> # basic tests + python -m pytest <spacy-directory> --slow # basic and slow tests + python -m pytest <spacy-directory> --models --all # basic and all model tests + python -m pytest <spacy-directory> --models --en # basic and English model tests diff --git a/website/docs/usage/language-processing-pipeline.jade b/website/docs/usage/language-processing-pipeline.jade index c372dfbf4..03f6c28f5 100644 --- a/website/docs/usage/language-processing-pipeline.jade +++ b/website/docs/usage/language-processing-pipeline.jade @@ -2,127 +2,352 @@ include ../../_includes/_mixins -p - | The standard entry point into spaCy is the #[code spacy.load()] - | function, which constructs a language processing pipeline. The standard - | variable name for the language processing pipeline is #[code nlp], for - | Natural Language Processing. The #[code nlp] variable is usually an - | instance of class #[code spacy.language.Language]. For English, the - | #[code spacy.en.English] class is the default. ++h(2, "101") Pipelines 101 + +include _spacy-101/_pipelines + ++h(2, "pipelines") How pipelines work p - | You'll use the nlp instance to produce #[+api("doc") #[code Doc]] - | objects. You'll then use the #[code Doc] object to access linguistic - | annotations to help you with whatever text processing task you're - | trying to do. + | spaCy makes it very easy to create your own pipelines consisting of + | reusable components – this includes spaCy's default tensorizer, tagger, + | parser and entity regcognizer, but also your own custom processing + | functions. A pipeline component can be added to an already existing + | #[code nlp] object, specified when initialising a #[code Language] class, + | or defined within a + | #[+a("/docs/usage/saving-loading#models-generating") model package]. + +p + | When you load a model, spaCy first consults the model's + | #[+a("/docs/usage/saving-loading#models-generating") meta.json]. The + | meta typically includes the model details, the ID of a language class, + | and an optional list of pipeline components. spaCy then does the + | following: + ++aside-code("meta.json (excerpt)", "json"). + { + "name": "example_model", + "lang": "en" + "description": "Example model for spaCy", + "pipeline": ["token_vectors", "tagger"] + } + ++list("numbers") + +item + | Look up #[strong pipeline IDs] in the available + | #[strong pipeline factories]. + +item + | Initialise the #[strong pipeline components] by calling their + | factories with the #[code Vocab] as an argument. This gives each + | factory and component access to the pipeline's shared data, like + | strings, morphology and annotation scheme. + +item + | Load the #[strong language class and data] for the given ID via + | #[+api("util.get_lang_class") #[code get_lang_class]]. + +item + | Pass the path to the #[strong model data] to the #[code Language] + | class and return it. + +p + | So when you call this... +code. - import spacy # See "Installing spaCy" - nlp = spacy.load('en') # You are here. - doc = nlp(u'Hello, spacy!') # See "Using the pipeline" - print((w.text, w.pos_) for w in doc) # See "Doc, Span and Token" - -+aside("Why do we have to preload?") - | Loading the models takes ~200x longer than - | processing a document. We therefore want to amortize the start-up cost - | across multiple invocations. It's often best to wrap the pipeline as a - | singleton. The library avoids doing that for you, because it's a - | difficult design to back out of. - -p The #[code load] function takes the following positional arguments: - -+table([ "Name", "Description" ]) - +row - +cell #[code lang_id] - +cell - | An ID that is resolved to a class or factory function by - | #[code spacy.util.get_lang_class()]. Common values are - | #[code 'en'] for the English pipeline, or #[code 'de'] for the - | German pipeline. You can register your own factory function or - | class with #[code spacy.util.set_lang_class()]. + nlp = spacy.load('en') p - | All keyword arguments are passed forward to the pipeline factory. No - | keyword arguments are required. The built-in factories (e.g. - | #[code spacy.en.English], #[code spacy.de.German]), which are subclasses - | of #[+api("language") #[code Language]], respond to the following - | keyword arguments: + | ... the model tells spaCy to use the pipeline + | #[code ["tensorizer", "tagger", "parser", "ner"]]. spaCy will then look + | up each string in its internal factories registry and initialise the + | individual components. It'll then load #[code spacy.lang.en.English], + | pass it the path to the model's data directory, and return it for you + | to use as the #[code nlp] object. -+table([ "Name", "Description"]) +p + | When you call #[code nlp] on a text, spaCy will #[strong tokenize] it and + | then #[strong call each component] on the #[code Doc], in order. + | Components all return the modified document, which is then processed by + | the component next in the pipeline. + ++code("The pipeline under the hood"). + doc = nlp.make_doc(u'This is a sentence') + for proc in nlp.pipeline: + doc = proc(doc) + ++h(2, "creating") Creating pipeline components and factories + +p + | spaCy lets you customise the pipeline with your own components. Components + | are functions that receive a #[code Doc] object, modify and return it. + | If your component is stateful, you'll want to create a new one for each + | pipeline. You can do that by defining and registering a factory which + | receives the shared #[code Vocab] object and returns a component. + ++h(3, "creating-component") Creating a component + +p + | A component receives a #[code Doc] object and + | #[strong performs the actual processing] – for example, using the current + | weights to make a prediction and set some annotation on the document. By + | adding a component to the pipeline, you'll get access to the #[code Doc] + | at any point #[strong during] processing – instead of only being able to + | modify it afterwards. + ++aside-code("Example"). + def my_component(doc): + # do something to the doc here + return doc + ++table(["Argument", "Type", "Description"]) +row - +cell #[code path] - +cell - | Where to load the data from. If None, the default data path is - | fetched via #[code spacy.util.get_data_path()]. You can - | configure this default using #[code spacy.util.set_data_path()]. - | The data path is expected to be either a string, or an object - | responding to the #[code pathlib.Path] interface. If the path is - | a string, it will be immediately transformed into a - | #[code pathlib.Path] object. spaCy promises to never manipulate - | or open file-system paths as strings. All access to the - | file-system is done via the #[code pathlib.Path] interface. - | spaCy also promises to never check the type of path objects. - | This allows you to customize the loading behaviours in arbitrary - | ways, by creating your own object that implements the - | #[code pathlib.Path] interface. + +cell #[code doc] + +cell #[code Doc] + +cell The #[code Doc] object processed by the previous component. - +row - +cell #[code pipeline] - +cell - | A sequence of functions that take the Doc object and modify it - | in-place. See - | #[+a("customizing-pipeline") Customizing the pipeline]. + +footrow + +cell returns + +cell #[code Doc] + +cell The #[code Doc] object processed by this pipeline component. - +row - +cell #[code create_pipeline] - +cell - | Callback to construct the pipeline sequence. It should accept - | the #[code nlp] instance as its only argument, and return a - | sequence of functions that take the #[code Doc] object and - | modify it in-place. - | See #[+a("customizing-pipeline") Customizing the pipeline]. If - | a value is supplied to the pipeline keyword argument, the - | #[code create_pipeline] keyword argument is ignored. +p + | When creating a new #[code Language] class, you can pass it a list of + | pipeline component functions to execute in that order. You can also + | add it to an existing pipeline by modifying #[code nlp.pipeline] – just + | be careful not to overwrite a pipeline or its components by accident! - +row - +cell #[code make_doc] - +cell A function that takes the input and returns a document object. ++code. + # Create a new Language object with a pipeline + from spacy.language import Language + nlp = Language(pipeline=[my_component]) - +row - +cell #[code create_make_doc] - +cell - | Callback to construct the #[code make_doc] function. It should - | accept the #[code nlp] instance as its only argument. To use the - | built-in annotation processes, it should return an object of - | type #[code Doc]. If a value is supplied to the #[code make_doc] - | keyword argument, the #[code create_make_doc] keyword argument - | is ignored. + # Modify an existing pipeline + nlp = spacy.load('en') + nlp.pipeline.append(my_component) ++h(3, "creating-factory") Creating a factory + +p + | A factory is a #[strong function that returns a pipeline component]. + | It's called with the #[code Vocab] object, to give it access to the + | shared data between components – for example, the strings, morphology, + | vectors or annotation scheme. Factories are useful for creating + | #[strong stateful components], especially ones which + | #[strong depend on shared data]. + ++aside-code("Example"). + def my_factory(vocab): + # load some state + def my_component(doc): + # process the doc + return doc + return my_component + ++table(["Argument", "Type", "Description"]) +row +cell #[code vocab] - +cell Supply a pre-built Vocab instance, instead of constructing one. - - +row - +cell #[code add_vectors] + +cell #[code Vocab] +cell - | Callback that installs word vectors into the Vocab instance. The - | #[code add_vectors] callback should take a - | #[+api("vocab") #[code Vocab]] instance as its only argument, - | and set the word vectors and #[code vectors_length] in-place. See - | #[+a("word-vectors-similarities") Word Vectors and Similarities]. + | Shared data between components, including strings, morphology, + | vectors etc. - +row - +cell #[code tagger] - +cell Supply a pre-built tagger, instead of creating one. + +footrow + +cell returns + +cell callable + +cell The pipeline component. - +row - +cell #[code parser] - +cell Supply a pre-built parser, instead of creating one. +p + | By creating a factory, you're essentially telling spaCy how to get the + | pipeline component #[strong once the vocab is available]. Factories need to + | be registered via #[+api("spacy#set_factory") #[code set_factory()]] and + | by assigning them a unique ID. This ID can be added to the pipeline as a + | string. When creating a pipeline, you're free to mix strings and + | callable components: - +row - +cell #[code entity] - +cell Supply a pre-built entity recognizer, instead of creating one. ++code. + spacy.set_factory('my_factory', my_factory) + nlp = Language(pipeline=['my_factory', my_other_component]) - +row - +cell #[code matcher] - +cell Supply a pre-built matcher, instead of creating one. +p + | If spaCy comes across a string in the pipeline, it will try to resolve it + | by looking it up in the available factories. The factory will then be + | initialised with the #[code Vocab]. Providing factory names instead of + | callables also makes it easy to specify them in the model's + | #[+a("/docs/usage/saving-loading#models-generating") meta.json]. If you're + | training your own model and want to use one of spaCy's default components, + | you won't have to worry about finding and implementing it either – to use + | the default tagger, simply add #[code "tagger"] to the pipeline, and + | #[strong spaCy will know what to do]. + + ++infobox("Important note") + | Because factories are #[strong resolved on initialisation] of the + | #[code Language] class, it's #[strong not possible] to add them to the + | pipeline afterwards, e.g. by modifying #[code nlp.pipeline]. This only + | works with individual component functions. To use factories, you need to + | create a new #[code Language] object, or generate a + | #[+a("/docs/usage/saving-loading#models-generating") model package] with + | a custom pipeline. + ++aside("Real-world examples") + | To see real-world examples of pipeline factories and components in action, + | you can have a look at the source of spaCy's built-in components, e.g. + | the #[+api("tagger") #[code Tagger]], #[+api("parser") #[code Parser]] or + | #[+api("entityrecognizer") #[code EntityRecongnizer]]. + ++h(2, "example1") Example: Custom sentence segmentation logic + +p + | Let's say you want to implement custom logic to improve spaCy's sentence + | boundary detection. Currently, sentence segmentation is based on the + | dependency parse, which doesn't always produce ideal results. The custom + | logic should therefore be applied #[strong after] tokenization, but + | #[strong before] the dependency parsing – this way, the parser can also + | take advantage of the sentence boundaries. + ++code. + def sbd_component(doc): + for i, token in enumerate(doc[:-2]): + # define sentence start if period + titlecase token + if token.text == '.' and doc[i+1].is_title: + doc[i+1].sent_start = True + return doc + +p + | In this case, we simply want to add the component to the existing + | pipeline of the English model. We can do this by inserting it at index 0 + | of #[code nlp.pipeline]: + ++code. + nlp = spacy.load('en') + nlp.pipeline.insert(0, sbd_component) + +p + | When you call #[code nlp] on some text, spaCy will tokenize it to create + | a #[code Doc] object, and first call #[code sbd_component] on it, followed + | by the model's default pipeline. + ++h(2, "example2") Example: Sentiment model + +p + | Let's say you have trained your own document sentiment model on English + | text. After tokenization, you want spaCy to first execute the + | #[strong default tensorizer], followed by a custom + | #[strong sentiment component] that adds a #[code .sentiment] + | property to the #[code Doc], containing your model's sentiment precition. + +p + | Your component class will have a #[code from_disk()] method that spaCy + | calls to load the model data. When called, the component will compute + | the sentiment score, add it to the #[code Doc] and return the modified + | document. Optionally, the component can include an #[code update()] method + | to allow training the model. + ++code. + import pickle + from pathlib import Path + + class SentimentComponent(object): + def __init__(self, vocab): + self.weights = None + + def __call__(self, doc): + doc.sentiment = sum(self.weights*doc.vector) # set sentiment property + return doc + + def from_disk(self, path): # path = model path + factory ID ('sentiment') + self.weights = pickle.load(Path(path) / 'weights.bin') # load weights + return self + + def update(self, doc, gold): # update weights – allows training! + prediction = sum(self.weights*doc.vector) + self.weights -= 0.001*doc.vector*(prediction-gold.sentiment) + +p + | The factory will initialise the component with the #[code Vocab] object. + | To be able to add it to your model's pipeline as #[code 'sentiment'], + | it also needs to be registered via + | #[+api("spacy#set_factory") #[code set_factory()]]. + ++code. + def sentiment_factory(vocab): + component = SentimentComponent(vocab) # initialise component + return component + + spacy.set_factory('sentiment', sentiment_factory) + +p + | The above code should be #[strong shipped with your model]. You can use + | the #[+api("cli#package") #[code package]] command to create all required + | files and directories. The model package will include an + | #[+src(gh("spacy-dev-resources", "templates/model/en_model_name/__init__.py")) __init__.py] + | with a #[code load()] method, that will initialise the language class with + | the model's pipeline and call the #[code from_disk()] method to load + | the model data. + +p + | In the model package's meta.json, specify the language class and pipeline + | IDs: + ++code("meta.json (excerpt)", "json"). + { + "name": "sentiment_model", + "lang": "en", + "version": "1.0.0", + "spacy_version": ">=2.0.0,<3.0.0", + "pipeline": ["tensorizer", "sentiment"] + } + +p + | When you load your new model, spaCy will call the model's #[code load()] + | method. This will return a #[code Language] object with a pipeline + | containing the default tensorizer, and the sentiment component returned + | by your custom #[code "sentiment"] factory. + ++code. + nlp = spacy.load('en_sentiment_model') + doc = nlp(u'I love pizza') + assert doc.sentiment + ++infobox("Saving and loading models") + | For more information and a detailed guide on how to package your model, + | see the documentation on + | #[+a("/docs/usage/saving-loading#models") saving and loading models]. + ++h(2, "disabling") Disabling pipeline components + +p + | If you don't need a particular component of the pipeline – for + | example, the tagger or the parser, you can disable loading it. This can + | sometimes make a big difference and improve loading speed. Disabled + | component names can be provided to #[+api("spacy#load") #[code spacy.load()]], + | #[+api("language#from_disk") #[code Language.from_disk()]] or the + | #[code nlp] object itself as a list: + ++code. + nlp = spacy.load('en', disable['parser', 'tagger']) + nlp = English().from_disk('/model', disable=['tensorizer', 'ner']) + doc = nlp(u"I don't want parsed", disable=['parser']) + +p + | Note that you can't write directly to #[code nlp.pipeline], as this list + | holds the #[em actual components], not the IDs. However, if you know the + | order of the components, you can still slice the list: + ++code. + nlp = spacy.load('en') + nlp.pipeline = nlp.pipeline[:2] # only use the first two components + ++infobox("Important note: disabling pipeline components") + .o-block + | Since spaCy v2.0 comes with better support for customising the + | processing pipeline components, the #[code parser], #[code tagger] + | and #[code entity] keyword arguments have been replaced with + | #[code disable], which takes a list of pipeline component names. + | This lets you disable both default and custom components when loading + | a model, or initialising a Language class via + | #[+api("language-from_disk") #[code from_disk]]. + +code-new. + nlp = spacy.load('en', disable=['tagger', 'ner']) + doc = nlp(u"I don't want parsed", disable=['parser']) + +code-old. + nlp = spacy.load('en', tagger=False, entity=False) + doc = nlp(u"I don't want parsed", parse=False) diff --git a/website/docs/usage/lightning-tour.jade b/website/docs/usage/lightning-tour.jade index 967d0c61e..0be3a55be 100644 --- a/website/docs/usage/lightning-tour.jade +++ b/website/docs/usage/lightning-tour.jade @@ -4,25 +4,215 @@ include ../../_includes/_mixins p | The following examples and code snippets give you an overview of spaCy's - | functionality and its usage. + | functionality and its usage. If you're new to spaCy, make sure to check + | out the #[+a("/docs/usage/spacy-101") spaCy 101 guide]. -+h(2, "models") Install and load models ++h(2, "models") Install models and process text +code(false, "bash"). python -m spacy download en + python -m spacy download de +code. import spacy nlp = spacy.load('en') + doc = nlp(u'Hello, world. Here are two sentences.') -+h(2, "examples-resources") Load resources and process text + nlp_de = spacy.load('de') + doc_de = nlp_de(u'Ich bin ein Berliner.') + ++infobox + | #[strong API:] #[+api("spacy#load") #[code spacy.load()]] + | #[strong Usage:] #[+a("/docs/usage/models") Models], + | #[+a("/docs/usage/spacy-101") spaCy 101] + ++h(2, "examples-tokens-sentences") Get tokens, noun chunks & sentences + +tag-model("dependency parse") + ++code. + doc = nlp(u"Peach emoji is where it has always been. Peach is the superior " + u"emoji. It's outranking eggplant 🍑 ") + + assert doc[0].text == u'Peach' + assert doc[1].text == u'emoji' + assert doc[-1].text == u'🍑' + assert doc[17:19].text == u'outranking eggplant' + assert list(doc.noun_chunks)[0].text == u'Peach emoji' + + sentences = list(doc.sents) + assert len(sentences) == 3 + assert sentences[1].text == u'Peach is the superior emoji.' + ++infobox + | #[strong API:] #[+api("doc") #[code Doc]], #[+api("token") #[code Token]] + | #[strong Usage:] #[+a("/docs/usage/spacy-101") spaCy 101] + ++h(2, "examples-pos-tags") Get part-of-speech tags and flags + +tag-model("tagger") + ++code. + doc = nlp(u'Apple is looking at buying U.K. startup for $1 billion') + apple = doc[0] + assert [apple.pos_, apple.pos] == [u'PROPN', 17049293600679659579] + assert [apple.tag_, apple.tag] == [u'NNP', 15794550382381185553] + assert [apple.shape_, apple.shape] == [u'Xxxxx', 16072095006890171862] + assert apple.is_alpha == True + assert apple.is_punct == False + + billion = doc[10] + assert billion.is_digit == False + assert billion.like_num == True + assert billion.like_email == False + ++infobox + | #[strong API:] #[+api("token") #[code Token]] + | #[strong Usage:] #[+a("/docs/usage/pos-tagging") Part-of-speech tagging] + ++h(2, "examples-hashes") Use hash values for any string + ++code. + doc = nlp(u'I love coffee') + coffee_hash = nlp.vocab.strings[u'coffee'] # 3197928453018144401 + coffee_text = nlp.vocab.strings[coffee_hash] # 'coffee' + + assert doc[2].orth == coffee_hash == 3197928453018144401 + assert doc[2].text == coffee_text == u'coffee' + + beer_hash = doc.vocab.strings.add(u'beer') # 3073001599257881079 + beer_text = doc.vocab.strings[beer_hash] # 'beer' + + unicorn_hash = doc.vocab.strings.add(u'🦄 ') # 18234233413267120783 + unicorn_text = doc.vocab.strings[unicorn_hash] # '🦄 ' + ++infobox + | #[strong API:] #[+api("stringstore") #[code stringstore]] + | #[strong Usage:] #[+a("/docs/usage/spacy-101#vocab") Vocab, hashes and lexemes 101] + ++h(2, "examples-entities") Recongnise and update named entities + +tag-model("NER") + ++code. + doc = nlp(u'San Francisco considers banning sidewalk delivery robots') + ents = [(ent.text, ent.start_char, ent.end_char, ent.label_) for ent in doc.ents] + assert ents == [(u'San Francisco', 0, 13, u'GPE')] + + from spacy.tokens import Span + doc = nlp(u'Netflix is hiring a new VP of global policy') + doc.ents = [Span(doc, 0, 1, label=doc.vocab.strings[u'ORG'])] + ents = [(ent.start_char, ent.end_char, ent.label_) for ent in doc.ents] + assert ents == [(0, 7, u'ORG')] + ++infobox + | #[strong Usage:] #[+a("/docs/usage/entity-recognition") Named entity recognition] + ++h(2, "displacy") Visualize a dependency parse and named entities in your browser + +tag-model("dependency parse", "NER") + ++aside + .u-text-center(style="overflow: auto"). + + + This + DT + + + is + VBZ + + + a + DT + + + sentence. + NN + + + + nsubj + + + + + det + + + + + attr + + + + ++code. + from spacy import displacy + + doc_dep = nlp(u'This is a sentence.') + displacy.serve(doc_dep, style='dep') + + doc_ent = nlp(u'When Sebastian Thrun started working on self-driving cars at Google ' + u'in 2007, few people outside of the company took him seriously.') + displacy.serve(doc_ent, style='ent') + ++infobox + | #[strong API:] #[+api("displacy") #[code displacy]] + | #[strong Usage:] #[+a("/docs/usage/visualizers") Visualizers] + ++h(2, "examples-word-vectors") Get word vectors and similarity + +tag-model("word vectors") + ++code. + doc = nlp(u"Apple and banana are similar. Pasta and hippo aren't.") + apple = doc[0] + banana = doc[2] + pasta = doc[6] + hippo = doc[8] + assert apple.similarity(banana) > pasta.similarity(hippo) + assert apple.has_vector, banana.has_vector, pasta.has_vector, hippo.has_vector + ++infobox + | #[strong Usage:] #[+a("/docs/usage/word-vectors-similarities") Word vectors and similarity] + ++h(2, "examples-serialization") Simple and efficient serialization +code. import spacy - en_nlp = spacy.load('en') - de_nlp = spacy.load('de') - en_doc = en_nlp(u'Hello, world. Here are two sentences.') - de_doc = de_nlp(u'ich bin ein Berliner.') + from spacy.tokens.doc import Doc + from spacy.vocab import Vocab + + nlp = spacy.load('en') + moby_dick = open('moby_dick.txt', 'r').read() + doc = nlp(moby_dick) + doc.to_disk('/moby_dick.bin') + + new_doc = Doc(Vocab()).from_disk('/moby_dick.bin') + ++infobox + | #[strong API:] #[+api("language") #[code Language]], + | #[+api("doc") #[code Doc]] + | #[strong Usage:] #[+a("/docs/usage/saving-loading") Saving and loading] + ++h(2, "rule-matcher") Match text with token rules + ++code. + import spacy + from spacy.matcher import Matcher + + nlp = spacy.load('en') + matcher = Matcher(nlp.vocab) + + def set_sentiment(matcher, doc, i, matches): + doc.sentiment += 0.1 + + pattern1 = [{'ORTH': 'Google'}, {'UPPER': 'I'}, {'ORTH': '/'}, {'UPPER': 'O'}] + pattern2 = [[{'ORTH': emoji, 'OP': '+'}] for emoji in ['😀', '😂', '🤣', '😍']] + matcher.add('GoogleIO', None, pattern1) # match "Google I/O" or "Google i/o" + matcher.add('HAPPY', set_sentiment, *pattern2) # match one or more happy emoji + matches = nlp(LOTS_OF TEXT) + ++infobox + | #[strong API:] #[+api("matcher") #[code Matcher]] + | #[strong Usage:] #[+a("/docs/usage/rule-based-matching") Rule-based matching] +h(2, "multi-threaded") Multi-threaded generator @@ -35,37 +225,25 @@ p if i == 100: break -+h(2, "examples-tokens-sentences") Get tokens and sentences ++infobox + | #[strong API:] #[+api("doc") #[code Doc]] + | #[strong Usage:] #[+a("/docs/usage/production-usage") Production usage] + ++h(2, "examples-dependencies") Get syntactic dependencies + +tag-model("dependency parse") +code. - token = doc[0] - sentence = next(doc.sents) - assert token is sentence[0] - assert sentence.text == 'Hello, world.' + def dependency_labels_to_root(token): + """Walk up the syntactic tree, collecting the arc labels.""" + dep_labels = [] + while token.head is not token: + dep_labels.append(token.dep) + token = token.head + return dep_labels -+h(2, "examples-integer-ids") Use integer IDs for any string - -+code. - hello_id = nlp.vocab.strings['Hello'] - hello_str = nlp.vocab.strings[hello_id] - - assert token.orth == hello_id == 3125 - assert token.orth_ == hello_str == 'Hello' - -+h(2, "examples-string-views-flags") Get and set string views and flags - -+code. - assert token.shape_ == 'Xxxxx' - for lexeme in nlp.vocab: - if lexeme.is_alpha: - lexeme.shape_ = 'W' - elif lexeme.is_digit: - lexeme.shape_ = 'D' - elif lexeme.is_punct: - lexeme.shape_ = 'P' - else: - lexeme.shape_ = 'M' - assert token.shape_ == 'W' ++infobox + | #[strong API:] #[+api("token") #[code Token]] + | #[strong Usage:] #[+a("/docs/usage/dependency-parse") Using the dependency parse] +h(2, "examples-numpy-arrays") Export to numpy arrays @@ -80,107 +258,25 @@ p assert doc[0].like_url == doc_array[0, 1] assert list(doc_array[:, 1]) == [t.like_url for t in doc] -+h(2, "examples-word-vectors") Word vectors - -+code. - doc = nlp("Apples and oranges are similar. Boots and hippos aren't.") - - apples = doc[0] - oranges = doc[2] - boots = doc[6] - hippos = doc[8] - - assert apples.similarity(oranges) > boots.similarity(hippos) - -+h(2, "examples-pos-tags") Part-of-speech tags - -+code. - from spacy.parts_of_speech import ADV - - def is_adverb(token): - return token.pos == spacy.parts_of_speech.ADV - - # These are data-specific, so no constants are provided. You have to look - # up the IDs from the StringStore. - NNS = nlp.vocab.strings['NNS'] - NNPS = nlp.vocab.strings['NNPS'] - def is_plural_noun(token): - return token.tag == NNS or token.tag == NNPS - - def print_coarse_pos(token): - print(token.pos_) - - def print_fine_pos(token): - print(token.tag_) - -+h(2, "examples-dependencies") Syntactic dependencies - -+code. - def dependency_labels_to_root(token): - '''Walk up the syntactic tree, collecting the arc labels.''' - dep_labels = [] - while token.head is not token: - dep_labels.append(token.dep) - token = token.head - return dep_labels - -+h(2, "examples-entities") Named entities - -+code. - def iter_products(docs): - for doc in docs: - for ent in doc.ents: - if ent.label_ == 'PRODUCT': - yield ent - - def word_is_in_entity(word): - return word.ent_type != 0 - - def count_parent_verb_by_person(docs): - counts = defaultdict(lambda: defaultdict(int)) - for doc in docs: - for ent in doc.ents: - if ent.label_ == 'PERSON' and ent.root.head.pos == VERB: - counts[ent.orth_][ent.root.head.lemma_] += 1 - return counts - -+h(2, "examples-inline") Calculate inline mark-up on original string ++h(2, "examples-inline") Calculate inline markup on original string +code. def put_spans_around_tokens(doc, get_classes): - '''Given some function to compute class names, put each token in a - span element, with the appropriate classes computed. - - All whitespace is preserved, outside of the spans. (Yes, I know HTML - won't display it. But the point is no information is lost, so you can - calculate what you need, e.g.
tags,

tags, etc.) - ''' + """Given some function to compute class names, put each token in a + span element, with the appropriate classes computed. All whitespace is + preserved, outside of the spans. (Of course, HTML won't display more than + one whitespace character it – but the point is, no information is lost + and you can calculate what you need, e.g. <br />, <p> etc.) + """ output = [] - template = '{word}{space}' + html = '<span class="{classes}">{word}</span>{space}' for token in doc: if token.is_space: - output.append(token.orth_) + output.append(token.text) else: - output.append( - template.format( - classes=' '.join(get_classes(token)), - word=token.orth_, - space=token.whitespace_)) + classes = ' '.join(get_classes(token)) + output.append(html.format(classes=classes, word=token.text, space=token.whitespace_)) string = ''.join(output) string = string.replace('\n', '') string = string.replace('\t', ' ') return string - -+h(2, "examples-binary") Efficient binary serialization - -+code. - import spacy - from spacy.tokens.doc import Doc - - byte_string = doc.to_bytes() - open('moby_dick.bin', 'wb').write(byte_string) - - nlp = spacy.load('en') - for byte_string in Doc.read_bytes(open('moby_dick.bin', 'rb')): - doc = Doc(nlp.vocab) - doc.from_bytes(byte_string) diff --git a/website/docs/usage/models.jade b/website/docs/usage/models.jade index 9bb75ba9a..39c37a816 100644 --- a/website/docs/usage/models.jade +++ b/website/docs/usage/models.jade @@ -8,28 +8,25 @@ p | other module. They're versioned and can be defined as a dependency in your | #[code requirements.txt]. Models can be installed from a download URL or | a local directory, manually or via #[+a("https://pypi.python.org/pypi/pip") pip]. - | Their data can be located anywhere on your file system. To make a model - | available to spaCy, all you need to do is create a "shortcut link", an - | internal alias that tells spaCy where to find the data files for a specific - | model name. + | Their data can be located anywhere on your file system. -+aside-code("Quickstart"). - # Install spaCy and download English model - pip install spacy - python -m spacy download en ++aside("Important note") + | If you're upgrading to spaCy v1.7.x or v2.x, you need to + | #[strong download the new models]. If you've trained statistical models + | that use spaCy's annotations, you should #[strong retrain your models] + | after updating spaCy. If you don't retrain, you may suffer train/test + | skew, which might decrease your accuracy. - # Usage in Python - import spacy - nlp = spacy.load('en') - doc = nlp(u'This is a sentence.') - -+infobox("Important note") - | Due to improvements in the English lemmatizer in v1.7.0, you need to - | #[strong download the new English models]. The German model is still - | compatible. If you've trained statistical models that use spaCy's - | annotations, you should #[strong retrain your models after updating spaCy]. - | If you don't retrain your models, you may suffer train/test skew, which - | might decrease your accuracy. ++quickstart(QUICKSTART_MODELS, "Quickstart", "Install a default model, get the code to load it from within spaCy and an example to test it. For more options, see the section on available models below.") + for models, lang in MODELS + - var package = (models.length == 1) ? models[0] : models.find(function(m) { return m.def }) + +qs({lang: lang}) python -m spacy download #{lang} + +qs({lang: lang}, "divider") + +qs({lang: lang, load: "module"}, "python") import #{package.id} + +qs({lang: lang, load: "module"}, "python") nlp = #{package.id}.load() + +qs({lang: lang, load: "spacy"}, "python") nlp = spacy.load('#{lang}') + +qs({lang: lang, config: "example"}, "python") doc = nlp(u"#{EXAMPLE_SENTENCES[lang]}") + +qs({lang: lang, config: "example"}, "python") print([(w.text, w.pos_) for w in doc]) +h(2, "available") Available models @@ -49,19 +46,16 @@ include _models-list | The old models are also #[+a(gh("spacy") + "/tree/v1.6.0") attached to the v1.6.0 release]. | To download and install them manually, unpack the archive, drop the - | contained directory into #[code spacy/data] and load the model via - | #[code spacy.load('en')] or #[code spacy.load('de')]. - + | contained directory into #[code spacy/data]. p - | The easiest way to download a model is via spaCy's #[code download] - | command. It takes care of finding the best-matching model compatible with - | your spaCy installation. + | The easiest way to download a model is via spaCy's + | #[+api("cli#download") #[code download]] command. It takes care of + | finding the best-matching model compatible with your spaCy installation. +- var models = Object.keys(MODELS).map(function(lang) { return "python -m spacy download " + lang }) +code(false, "bash"). # out-of-the-box: download best-matching default model - python -m spacy download en - python -m spacy download de - python -m spacy download fr + #{Object.keys(MODELS).map(function(l) {return "python -m spacy download " + l}).join('\n')} # download best-matching version of specific model for your spaCy installation python -m spacy download en_core_web_md @@ -72,8 +66,8 @@ p p | The download command will #[+a("#download-pip") install the model] via | pip, place the package in your #[code site-packages] directory and create - | a #[+a("#usage") shortcut link] that lets you load the model by name. The - | shortcut link will be the same as the model name used in + | a #[+a("#usage") shortcut link] that lets you load the model by a custom + | name. The shortcut link will be the same as the model name used in | #[code spacy.download]. +code(false, "bash"). @@ -103,9 +97,19 @@ p p | By default, this will install the model into your #[code site-packages] - | directory. You can then create a #[+a("#usage") shortcut link] for your - | model to load it via #[code spacy.load()], or #[+a("usage-import") import it] - | as a Python module. + | directory. You can then use #[code spacy.load()] to load it via its + | package name, create a #[+a("#usage-link") shortcut link] to assign it a + | custom name, or #[+a("usage-import") import it] explicitly as a module. + | If you need to download models as part of an automated process, we + | recommend using pip with a direct link, instead of relying on spaCy's + | #[+api("cli#download") #[code download]] command. + ++infobox + | You can also add the direct download link to your application's + | #[code requirements.txt]. For more details, + | see the usage guide on + | #[+a("/docs/usage/production-use#models") working with models in production]. + +h(3, "download-manual") Manual download and installation @@ -121,34 +125,68 @@ p └── en_core_web_md-1.2.0.tar.gz # downloaded archive ├── meta.json # model meta data ├── setup.py # setup file for pip installation - └── en_core_web_md # model directory + └── en_core_web_md # 📦 model package ├── __init__.py # init for pip installation ├── meta.json # model meta data └── en_core_web_md-1.2.0 # model data p - | You can place the model data directory anywhere on your local file system. - | To use it with spaCy, simply assign it a name by creating a - | #[+a("#usage") shortcut link] for the data directory. + | You can place the #[strong model package directory] anywhere on your + | local file system. To use it with spaCy, simply assign it a name by + | creating a #[+a("#usage") shortcut link] for the data directory. +h(2, "usage") Using models with spaCy +p + | To load a model, use #[+api("spacy#load") #[code spacy.load()]] with the + | model's shortcut link, package name or a path to the data directory: + ++code. + import spacy + nlp = spacy.load('en') # load model with shortcut link "en" + nlp = spacy.load('en_core_web_sm') # load model package "en_core_web_sm" + nlp = spacy.load('/path/to/en_core_web_sm') # load package from a directory + + doc = nlp(u'This is a sentence.') + ++infobox("Tip: Preview model info") + | You can use the #[+api("cli#info") #[code info]] command or + | #[+api("spacy#info") #[code spacy.info()]] method to print a model's meta data + | before loading it. Each #[code Language] object with a loaded model also + | exposes the model's meta data as the attribute #[code meta]. For example, + | #[code nlp.meta['version']] will return the model's version. + ++h(3, "usage-link") Using custom shortcut links + p | While previous versions of spaCy required you to maintain a data directory - | containing the models for each installation, you can now choose how and - | where you want to keep your data files. To load the models conveniently - | from within spaCy, you can use the #[code spacy.link] command to create a - | symlink. This lets you set up custom shortcut links for models so you can - | load them by name. + | containing the models for each installation, you can now choose + | #[strong how and where you want to keep your data]. For example, you could + | download all models manually and put them into a local directory. + | Whenever your spaCy projects need a models, you create a shortcut link to + | tell spaCy to load it from there. This means you'll never end up with + | duplicate data. + +p + | The #[+api("cli#link") #[code link]] command will create a symlink + | in the #[code spacy/data] directory. + ++aside("Why does spaCy use symlinks?") + | Symlinks were originally introduced to maintain backwards compatibility, + | as older versions expected model data to live within #[code spacy/data]. + | However, we decided to keep using them in v2.0 instead of opting for + | a config file. There'll always be a need for assigning and saving custom + | model names or IDs. And your system already comes with a native solution + | to mapping unicode aliases to file paths: symbolic links. +code(false, "bash"). python -m spacy link [package name or path] [shortcut] [--force] p - | The first argument is the package name (if the model was installed via - | pip), or a local path to the the data directory. The second argument is - | the internal name you want to use for the model. Setting the #[code --force] - | flag will overwrite any existing links. + | The first argument is the #[strong package name] (if the model was + | installed via pip), or a local path to the the #[strong model package]. + | The second argument is the internal name you want to use for the model. + | Setting the #[code --force] flag will overwrite any existing links. +code("Examples", "bash"). # set up shortcut link to load installed package as "en_default" @@ -157,58 +195,52 @@ p # set up shortcut link to load local model as "my_amazing_model" python -m spacy link /Users/you/model my_amazing_model -+h(3, "usage-loading") Loading models - -p - | To load a model, use #[code spacy.load()] with the model's shortcut link. - -+code. - import spacy - nlp = spacy.load('en_default') - doc = nlp(u'This is a sentence.') - -p - | You can also use the #[info] command or #[code info()] method to print a model's meta data - | before loading it. Each #[code Language] object returned by #[code spacy.load()] - | also exposes the model's meta data as the attribute #[code meta]. - -+code(false, "bash"). - python -m spacy info en - # model meta data - -+code. - import spacy - spacy.info('en_default') - # model meta data - - nlp = spacy.load('en_default') - print(nlp.meta['version']) - # 1.2.0 ++infobox("Important note") + | In order to create a symlink, your user needs the #[strong required permissions]. + | If you've installed spaCy to a system directory and don't have admin + | privileges, the #[code spacy link] command may fail. The easiest solution + | is to re-run the command as admin, or use a #[code virtualenv]. For more + | info on this, see the + | #[+a("/docs/usage/#symlink-privilege") troubleshooting guide]. +h(3, "usage-import") Importing models as modules p - | If you've installed a model via pip, you can also #[code import] it - | directly and then call its #[code load()] method with no arguments: + | If you've installed a model via spaCy's downloader, or directly via pip, + | you can also #[code import] it and then call its #[code load()] method + | with no arguments: +code. - import spacy import en_core_web_md nlp = en_core_web_md.load() doc = nlp(u'This is a sentence.') +p + | How you choose to load your models ultimately depends on personal + | preference. However, #[strong for larger code bases], we usually recommend + | native imports, as this will make it easier to integrate models with your + | existing build process, continuous integration workflow and testing + | framework. It'll also prevent you from ever trying to load a model that + | is not installed, as your code will raise an #[code ImportError] + | immediately, instead of failing somewhere down the line when calling + | #[code spacy.load()]. + ++infobox + | For more details, see the usage guide on + | #[+a("/docs/usage/production-use#models") working with models in production]. + +h(2, "own-models") Using your own models p | If you've trained your own model, for example for | #[+a("/docs/usage/adding-languages") additional languages] or | #[+a("/docs/usage/train-ner") custom named entities], you can save its - | state using the #[code Language.save_to_directory()] method. To make the - | model more convenient to deploy, we recommend wrapping it as a Python - | package. + | state using the #[+api("language#to_disk") #[code Language.to_disk()]] + | method. To make the model more convenient to deploy, we recommend + | wrapping it as a Python package. +infobox("Saving and loading models") | For more information and a detailed guide on how to package your model, | see the documentation on - | #[+a("/docs/usage/saving-loading") saving and loading models]. + | #[+a("/docs/usage/saving-loading#models") saving and loading models]. diff --git a/website/docs/usage/pos-tagging.jade b/website/docs/usage/pos-tagging.jade index cded00b6c..dd72efeba 100644 --- a/website/docs/usage/pos-tagging.jade +++ b/website/docs/usage/pos-tagging.jade @@ -7,22 +7,12 @@ p | assigned to each token in the document. They're useful in rule-based | processes. They can also be useful features in some statistical models. -p - | To use spaCy's tagger, you need to have a data pack installed that - | includes a tagging model. Tagging models are included in the data - | downloads for English and German. After you load the model, the tagger - | is applied automatically, as part of the default pipeline. You can then - | access the tags using the #[+api("token") #[code Token.tag]] and - | #[+api("token") #[code token.pos]] attributes. For English, the tagger - | also triggers some simple rule-based morphological processing, which - | gives you the lemma as well. ++h(2, "101") Part-of-speech tagging 101 + +tag-model("tagger", "dependency parse") -+code("Usage"). - import spacy - nlp = spacy.load('en') - doc = nlp(u'They told us to duck.') - for word in doc: - print(word.text, word.lemma, word.lemma_, word.tag, word.tag_, word.pos, word.pos_) +include _spacy-101/_pos-deps + ++aside("Help – spaCy's output is wrong!") +h(2, "rule-based-morphology") Rule-based morphology @@ -63,7 +53,8 @@ p +list("numbers") +item - | The tokenizer consults a #[strong mapping table] + | The tokenizer consults a + | #[+a("/docs/usage/adding-languages#tokenizer-exceptions") mapping table] | #[code TOKENIZER_EXCEPTIONS], which allows sequences of characters | to be mapped to multiple tokens. Each token may be assigned a part | of speech and one or more morphological features. @@ -77,8 +68,9 @@ p +item | For words whose POS is not set by a prior process, a - | #[strong mapping table] #[code TAG_MAP] maps the tags to a - | part-of-speech and a set of morphological features. + | #[+a("/docs/usage/adding-languages#tag-map") mapping table] + | #[code TAG_MAP] maps the tags to a part-of-speech and a set of + | morphological features. +item | Finally, a #[strong rule-based deterministic lemmatizer] maps the diff --git a/website/docs/usage/processing-text.jade b/website/docs/usage/processing-text.jade deleted file mode 100644 index 4bd6132d2..000000000 --- a/website/docs/usage/processing-text.jade +++ /dev/null @@ -1,134 +0,0 @@ -//- 💫 DOCS > USAGE > PROCESSING TEXT - -include ../../_includes/_mixins - -p - | Once you have loaded the #[code nlp] object, you can call it as though - | it were a function. This allows you to process a single unicode string. - -+code. - doc = nlp(u'Hello, world! A three sentence document.\nWith new lines...') - -p - | The library should perform equally well with short or long documents. - | All algorithms are linear-time in the length of the string, and once the - | data is loaded, there's no significant start-up cost to consider. This - | means that you don't have to strategically merge or split your text — - | you should feel free to feed in either single tweets or whole novels. - -p - | If you run #[code nlp = spacy.load('en')], the #[code nlp] object will - | be an instance of #[code spacy.en.English]. This means that when you run - | #[code doc = nlp(text)], you're executing - | #[code spacy.en.English.__call__], which is implemented on its parent - | class, #[+api("language") #[code Language]]. - -+code. - doc = nlp.make_doc(text) - for proc in nlp.pipeline: - proc(doc) - -p - | I've tried to make sure that the #[code Language.__call__] function - | doesn't do any "heavy lifting", so that you won't have complicated logic - | to replicate if you need to make your own pipeline class. This is all it - | does. - -p - | The #[code .make_doc()] method and #[code .pipeline] attribute make it - | easier to customise spaCy's behaviour. If you're using the default - | pipeline, we can desugar one more time. - -+code. - doc = nlp.tokenizer(text) - nlp.tagger(doc) - nlp.parser(doc) - nlp.entity(doc) - -p Finally, here's where you can find out about each of those components: - -+table(["Name", "Source"]) - +row - +cell #[code tokenizer] - +cell #[+src(gh("spacy", "spacy/tokenizer.pyx")) spacy.tokenizer.Tokenizer] - - +row - +cell #[code tagger] - +cell #[+src(gh("spacy", "spacy/tagger.pyx")) spacy.pipeline.Tagger] - - +row - +cell #[code parser] - +cell #[+src(gh("spacy", "spacy/syntax/parser.pyx")) spacy.pipeline.DependencyParser] - - +row - +cell #[code entity] - +cell #[+src(gh("spacy", "spacy/syntax/parser.pyx")) spacy.pipeline.EntityRecognizer] - -+h(2, "multithreading") Multi-threading with #[code .pipe()] - -p - | If you have a sequence of documents to process, you should use the - | #[+api("language#pipe") #[code .pipe()]] method. The #[code .pipe()] - | method takes an iterator of texts, and accumulates an internal buffer, - | which it works on in parallel. It then yields the documents in order, - | one-by-one. After a long and bitter struggle, the global interpreter - | lock was freed around spaCy's main parsing loop in v0.100.3. This means - | that the #[code .pipe()] method will be significantly faster in most - | practical situations, because it allows shared memory parallelism. - -+code. - for doc in nlp.pipe(texts, batch_size=10000, n_threads=3): - pass - -p - | To make full use of the #[code .pipe()] function, you might want to - | brush up on Python generators. Here are a few quick hints: - -+list - +item - | Generator comprehensions can be written - | (#[code item for item in sequence]) - - +item - | The #[code itertools] built-in library and the #[code cytoolz] - | package provide a lot of handy generator tools - - +item - | Often you'll have an input stream that pairs text with some - | important metadata, e.g. a JSON document. To pair up the metadata - | with the processed #[code Doc] object, you should use the tee - | function to split the generator in two, and then #[code izip] the - | extra stream to the document stream. - -+h(2, "own-annotations") Bringing your own annotations - -p - | spaCy generally assumes by default that your data is raw text. However, - | sometimes your data is partially annotated, e.g. with pre-existing - | tokenization, part-of-speech tags, etc. The most common situation is - | that you have pre-defined tokenization. If you have a list of strings, - | you can create a #[code Doc] object directly. Optionally, you can also - | specify a list of boolean values, indicating whether each word has a - | subsequent space. - -+code. - doc = Doc(nlp.vocab, words=[u'Hello', u',', u'world', u'!'], spaces=[False, True, False, False]) - -p - | If provided, the spaces list must be the same length as the words list. - | The spaces list affects the #[code doc.text], #[code span.text], - | #[code token.idx], #[code span.start_char] and #[code span.end_char] - | attributes. If you don't provide a #[code spaces] sequence, spaCy will - | assume that all words are whitespace delimited. - -+code. - good_spaces = Doc(nlp.vocab, words=[u'Hello', u',', u'world', u'!'], spaces=[False, True, False, False]) - bad_spaces = Doc(nlp.vocab, words=[u'Hello', u',', u'world', u'!']) - assert bad_spaces.text == u'Hello , world !' - assert good_spaces.text == u'Hello, world!' - -p - | Once you have a #[+api("doc") #[code Doc]] object, you can write to its - | attributes to set the part-of-speech tags, syntactic dependencies, named - | entities and other attributes. For details, see the respective usage - | pages. diff --git a/website/docs/usage/production-use.jade b/website/docs/usage/production-use.jade new file mode 100644 index 000000000..70227e648 --- /dev/null +++ b/website/docs/usage/production-use.jade @@ -0,0 +1,147 @@ +//- 💫 DOCS > USAGE > PROCESSING TEXT + +include ../../_includes/_mixins + ++under-construction + ++h(2, "multithreading") Multi-threading with #[code .pipe()] + +p + | If you have a sequence of documents to process, you should use the + | #[+api("language#pipe") #[code Language.pipe()]] method. The method takes + | an iterator of texts, and accumulates an internal buffer, + | which it works on in parallel. It then yields the documents in order, + | one-by-one. After a long and bitter struggle, the global interpreter + | lock was freed around spaCy's main parsing loop in v0.100.3. This means + | that #[code .pipe()] will be significantly faster in most + | practical situations, because it allows shared memory parallelism. + ++code. + for doc in nlp.pipe(texts, batch_size=10000, n_threads=3): + pass + +p + | To make full use of the #[code .pipe()] function, you might want to + | brush up on #[strong Python generators]. Here are a few quick hints: + ++list + +item + | Generator comprehensions can be written as + | #[code (item for item in sequence)]. + + +item + | The + | #[+a("https://docs.python.org/2/library/itertools.html") #[code itertools] built-in library] + | and the + | #[+a("https://github.com/pytoolz/cytoolz") #[code cytoolz] package] + | provide a lot of handy #[strong generator tools]. + + +item + | Often you'll have an input stream that pairs text with some + | important meta data, e.g. a JSON document. To + | #[strong pair up the meta data] with the processed #[code Doc] + | object, you should use the #[code itertools.tee] function to split + | the generator in two, and then #[code izip] the extra stream to the + | document stream. + ++h(2, "own-annotations") Bringing your own annotations + +p + | spaCy generally assumes by default that your data is raw text. However, + | sometimes your data is partially annotated, e.g. with pre-existing + | tokenization, part-of-speech tags, etc. The most common situation is + | that you have pre-defined tokenization. If you have a list of strings, + | you can create a #[code Doc] object directly. Optionally, you can also + | specify a list of boolean values, indicating whether each word has a + | subsequent space. + ++code. + doc = Doc(nlp.vocab, words=[u'Hello', u',', u'world', u'!'], spaces=[False, True, False, False]) + +p + | If provided, the spaces list must be the same length as the words list. + | The spaces list affects the #[code doc.text], #[code span.text], + | #[code token.idx], #[code span.start_char] and #[code span.end_char] + | attributes. If you don't provide a #[code spaces] sequence, spaCy will + | assume that all words are whitespace delimited. + ++code. + good_spaces = Doc(nlp.vocab, words=[u'Hello', u',', u'world', u'!'], spaces=[False, True, False, False]) + bad_spaces = Doc(nlp.vocab, words=[u'Hello', u',', u'world', u'!']) + assert bad_spaces.text == u'Hello , world !' + assert good_spaces.text == u'Hello, world!' + +p + | Once you have a #[+api("doc") #[code Doc]] object, you can write to its + | attributes to set the part-of-speech tags, syntactic dependencies, named + | entities and other attributes. For details, see the respective usage + | pages. + ++h(2, "models") Working with models + +p + | If your application depends on one or more #[+a("/docs/usage/models") models], + | you'll usually want to integrate them into your continuous integration + | workflow and build process. While spaCy provides a range of useful helpers + | for downloading, linking and loading models, the underlying functionality + | is entirely based on native Python packages. This allows your application + | to handle a model like any other package dependency. + ++h(3, "models-download") Downloading and requiring model dependencies + +p + | spaCy's built-in #[+api("cli#download") #[code download]] command + | is mostly intended as a convenient, interactive wrapper. It performs + | compatibility checks and prints detailed error messages and warnings. + | However, if you're downloading models as part of an automated build + | process, this only adds an unecessary layer of complexity. If you know + | which models your application needs, you should be specifying them directly. + +p + | Because all models are valid Python packages, you can add them to your + | application's #[code requirements.txt]. If you're running your own + | internal PyPi installation, you can simply upload the models there. pip's + | #[+a("https://pip.pypa.io/en/latest/reference/pip_install/#requirements-file-format") requirements file format] + | supports both package names to download via a PyPi server, as well as direct + | URLs. + ++code("requirements.txt", "text"). + spacy>=2.0.0,<3.0.0 + -e #{gh("spacy-models")}/releases/download/en_core_web_sm-2.0.0/en_core_web_sm-2.0.0.tar.gz + +p + | All models are versioned and specify their spaCy dependency. This ensures + | cross-compatibility and lets you specify exact version requirements for + | each model. If you've trained your own model, you can use the + | #[+api("cli#package") #[code package]] command to generate the required + | meta data and turn it into a loadable package. + ++h(3, "models-loading") Loading and testing models + +p + | Downloading models directly via pip won't call spaCy's link + | #[+api("cli#link") #[code link]] command, which creates + | symlinks for model shortcuts. This means that you'll have to run this + | command separately, or use the native #[code import] syntax to load the + | models: + ++code. + import en_core_web_sm + nlp = en_core_web_sm.load() + +p + | In general, this approach is recommended for larger code bases, as it's + | more "native", and doesn't depend on symlinks or rely on spaCy's loader + | to resolve string names to model packages. If a model can't be + | imported, Python will raise an #[code ImportError] immediately. And if a + | model is imported but not used, any linter will catch that. + +p + | Similarly, it'll give you more flexibility when writing tests that + | require loading models. For example, instead of writing your own + | #[code try] and #[code except] logic around spaCy's loader, you can use + | #[+a("http://pytest.readthedocs.io/en/latest/") pytest]'s + | #[code importorskip()] method to only run a test if a specific model or + | model version is installed. Each model package exposes a #[code __version__] + | attribute which you can also use to perform your own version compatibility + | checks before loading a model. diff --git a/website/docs/usage/resources.jade b/website/docs/usage/resources.jade deleted file mode 100644 index 56e92a1e7..000000000 --- a/website/docs/usage/resources.jade +++ /dev/null @@ -1,118 +0,0 @@ -//- 💫 DOCS > USAGE > RESOURCES - -include ../../_includes/_mixins - -p Many of the associated tools and resources that we're developing alongside spaCy can be found in their own repositories. - -+h(2, "developer") Developer tools - -+table(["Name", "Description"]) - +row - +cell - +src(gh("spacy-models")) spaCy Models - - +cell - | Model releases for spaCy. - - +row - +cell - +src(gh("spacy-dev-resources")) spaCy Dev Resources - - +cell - | Scripts, tools and resources for developing spaCy, adding new - | languages and training new models. - - +row - +cell - +src("spacy-benchmarks") spaCy Benchmarks - - +cell - | Runtime performance comparison of spaCy against other NLP - | libraries. - - +row - +cell - +src(gh("spacy-services")) spaCy Services - - +cell - | REST microservices for spaCy demos and visualisers. - - +row - +cell - +src(gh("spacy-notebooks")) spaCy Notebooks - - +cell - | Jupyter notebooks for spaCy examples and tutorials. - -+h(2, "libraries") Libraries and projects -+table(["Name", "Description"]) - +row - +cell - +src(gh("sense2vec")) sense2vec - - +cell - | Use spaCy to go beyond vanilla - | #[+a("https://en.wikipedia.org/wiki/Word2vec") Word2vec]. - -+h(2, "utility") Utility libraries and dependencies - -+table(["Name", "Description"]) - +row - +cell - +src(gh("thinc")) Thinc - - +cell - | spaCy's Machine Learning library for NLP in Python. - - +row - +cell - +src(gh("cymem")) Cymem - - +cell - | Gate Cython calls to malloc/free behind Python ref-counted - | objects. - - +row - +cell - +src(gh("preshed")) Preshed - - +cell - | Cython hash tables that assume keys are pre-hashed - - +row - +cell - +src(gh("murmurhash")) MurmurHash - - +cell - | Cython bindings for - | #[+a("https://en.wikipedia.org/wiki/MurmurHash") MurmurHash2]. - -+h(2, "visualizers") Visualisers and demos - -+table(["Name", "Description"]) - +row - +cell - +src(gh("displacy")) displaCy.js - - +cell - | A lightweight dependency visualisation library for the modern - | web, built with JavaScript, CSS and SVG. - | #[+a(DEMOS_URL + "/displacy") Demo here]. - - +row - +cell - +src(gh("displacy-ent")) displaCy#[sup ENT] - - +cell - | A lightweight and modern named entity visualisation library - | built with JavaScript and CSS. - | #[+a(DEMOS_URL + "/displacy-ent") Demo here]. - - +row - +cell - +src(gh("sense2vec-demo")) sense2vec Demo - - +cell - | Source of our Semantic Analysis of the Reddit Hivemind - | #[+a(DEMOS_URL + "/sense2vec") demo] using - | #[+a(gh("sense2vec")) sense2vec]. diff --git a/website/docs/usage/rule-based-matching.jade b/website/docs/usage/rule-based-matching.jade index aea943a61..71400ea55 100644 --- a/website/docs/usage/rule-based-matching.jade +++ b/website/docs/usage/rule-based-matching.jade @@ -4,62 +4,203 @@ include ../../_includes/_mixins p | spaCy features a rule-matching engine that operates over tokens, similar - | to regular expressions. The rules can refer to token annotations and - | flags, and matches support callbacks to accept, modify and/or act on the - | match. The rule matcher also allows you to associate patterns with - | entity IDs, to allow some basic entity linking or disambiguation. + | to regular expressions. The rules can refer to token annotations (e.g. + | the token #[code text] or #[code tag_], and flags (e.g. #[code IS_PUNCT]). + | The rule matcher also lets you pass in a custom callback + | to act on matches – for example, to merge entities and apply custom labels. + | You can also associate patterns with entity IDs, to allow some basic + | entity linking or disambiguation. -p Here's a minimal example. We first add a pattern that specifies three tokens: +//-+aside("What about \"real\" regular expressions?") -+list("numbers") - +item A token whose lower-case form matches "hello" - +item A token whose #[code is_punct] flag is set to #[code True] - +item A token whose lower-case form matches "world" ++h(2, "adding-patterns") Adding patterns p - | Once we've added the pattern, we can use the #[code matcher] as a - | callable, to receive a list of #[code (ent_id, start, end)] tuples. - | Note that #[code LOWER] and #[code IS_PUNCT] are data attributes - | of #[code spacy.attrs]. + | Let's say we want to enable spaCy to find a combination of three tokens: + ++list("numbers") + +item + | A token whose #[strong lowercase form matches "hello"], e.g. "Hello" + | or "HELLO". + +item + | A token whose #[strong #[code is_punct] flag is set to #[code True]], + | i.e. any punctuation. + +item + | A token whose #[strong lowercase form matches "world"], e.g. "World" + | or "WORLD". +code. - from spacy.matcher import Matcher - matcher = Matcher(nlp.vocab) - matcher.add_pattern("HelloWorld", [{LOWER: "hello"}, {IS_PUNCT: True}, {LOWER: "world"}]) + [{'LOWER': 'hello'}, {'IS_PUNCT': True}, {'LOWER': 'world'}] - doc = nlp(u'Hello, world!') +p + | First, we initialise the #[code Matcher] with a vocab. The matcher must + | always share the same vocab with the documents it will operate on. We + | can now call #[+api("matcher#add") #[code matcher.add()]] with an ID and + | our custom pattern. The second argument lets you pass in an optional + | callback function to invoke on a successful match. For now, we set it + | to #[code None]. + ++code. + import spacy + from spacy.matcher import Matcher + + nlp = spacy.load('en') + matcher = Matcher(nlp.vocab) + # add match ID "HelloWorld" with no callback and one pattern + pattern = [{'LOWER': 'hello'}, {'IS_PUNCT': True}, {'LOWER': 'world'}] + matcher.add('HelloWorld', None, pattern) + + doc = nlp(u'Hello, world! Hello world!') matches = matcher(doc) p - | The returned matches include the ID, to let you associate the matches - | with the patterns. You can also group multiple patterns together, which - | is useful when you have a knowledge base of entities you want to match, - | and you want to write multiple patterns for each entity. - -+h(2, "entities-patterns") Entities and patterns + | The matcher returns a list of #[code (match_id, start, end)] tuples – in + | this case, #[code [('HelloWorld', 0, 2)]], which maps to the span + | #[code doc[0:2]] of our original document. Optionally, we could also + | choose to add more than one pattern, for example to also match sequences + | without punctuation between "hello" and "world": +code. - matcher.add_entity( - "GoogleNow", # Entity ID -- Helps you act on the match. - {"ent_type": "PRODUCT", "wiki_en": "Google_Now"}, # Arbitrary attributes (optional) - ) + matcher.add('HelloWorld', None, + [{'LOWER': 'hello'}, {'IS_PUNCT': True}, {'LOWER': 'world'}], + [{'LOWER': 'hello'}, {'LOWER': 'world'}]) - matcher.add_pattern( - "GoogleNow", # Entity ID -- Created if doesn't exist. - [ # The pattern is a list of *Token Specifiers*. - { # This Token Specifier matches tokens whose orth field is "Google" - ORTH: "Google" - }, - { # This Token Specifier matches tokens whose orth field is "Now" - ORTH: "Now" - } - ], - label=None # Can associate a label to the pattern-match, to handle it better. - ) +p + | By default, the matcher will only return the matches and + | #[strong not do anything else], like merge entities or assign labels. + | This is all up to you and can be defined individually for each pattern, + | by passing in a callback function as the #[code on_match] argument on + | #[code add()]. This is useful, because it lets you write entirely custom + | and #[strong pattern-specific logic]. For example, you might want to + | merge #[em some] patterns into one token, while adding entity labels for + | other pattern types. You shouldn't have to create different matchers for + | each of those processes. -+h(2, "quantifiers") Using quantifiers ++h(2, "on_match") Adding #[code on_match] rules -+table([ "Name", "Description", "Example"]) +p + | To move on to a more realistic example, let's say you're working with a + | large corpus of blog articles, and you want to match all mentions of + | "Google I/O" (which spaCy tokenizes as #[code ['Google', 'I', '/', 'O']]). + | To be safe, you only match on the uppercase versions, in case someone has + | written it as "Google i/o". You also add a second pattern with an added + | #[code {IS_DIGIT: True}] token – this will make sure you also match on + | "Google I/O 2017". If your pattern matches, spaCy should execute your + | custom callback function #[code add_event_ent]. + ++code. + import spacy + from spacy.matcher import Matcher + + nlp = spacy.load('en') + matcher = Matcher(nlp.vocab) + + # Get the ID of the 'EVENT' entity type. This is required to set an entity. + EVENT = nlp.vocab.strings['EVENT'] + + def add_event_ent(matcher, doc, i, matches): + # Get the current match and create tuple of entity label, start and end. + # Append entity to the doc's entity. (Don't overwrite doc.ents!) + match_id, start, end = matches[i] + doc.ents += ((EVENT, start, end),) + + matcher.add('GoogleIO', add_event_ent, + [{'ORTH': 'Google'}, {'UPPER': 'I'}, {'ORTH': '/'}, {'UPPER': 'O'}], + [{'ORTH': 'Google'}, {'UPPER': 'I'}, {'ORTH': '/'}, {'UPPER': 'O'}, {'IS_DIGIT': True}]) + +p + | In addition to mentions of "Google I/O", your data also contains some + | annoying pre-processing artefacts, like leftover HTML line breaks + | (e.g. #[code <br>] or #[code <BR/>]). While you're at it, + | you want to merge those into one token and flag them, to make sure you + | can easily ignore them later. So you add a second pattern and pass in a + | function #[code merge_and_flag]: + ++code. + # Add a new custom flag to the vocab, which is always False by default. + # BAD_HTML_FLAG will be the flag ID, which we can use to set it to True on the span. + BAD_HTML_FLAG = nlp.vocab.add_flag(lambda text: False) + + def merge_and_flag(matcher, doc, i, matches): + match_id, start, end = matches[i] + span = doc[start : end] + span.merge(is_stop=True) # merge (and mark it as a stop word, just in case) + span.set_flag(BAD_HTML_FLAG, True) # set BAD_HTML_FLAG + + matcher.add('BAD_HTML', merge_and_flag, + [{'ORTH': '<'}, {'LOWER': 'br'}, {'ORTH': '>'}], + [{'ORTH': '<'}, {'LOWER': 'br/'}, {'ORTH': '>'}]) + ++aside("Tip: Visualizing matches") + | When working with entities, you can use #[+api("displacy") displaCy] + | to quickly generate a NER visualization from your updated #[code Doc], + | which can be exported as an HTML file: + + +code.o-no-block. + from spacy import displacy + html = displacy.render(doc, style='ent', page=True, + options={'ents': ['EVENT']}) + + | For more info and examples, see the usage guide on + | #[+a("/docs/usage/visualizers") visualizing spaCy]. + +p + | We can now call the matcher on our documents. The patterns will be + | matched in the order they occur in the text. The matcher will then + | iterate over the matches, look up the callback for the match ID + | that was matched, and invoke it. + ++code. + doc = nlp(LOTS_OF_TEXT) + matcher(doc) + +p + | When the callback is invoked, it is + | passed four arguments: the matcher itself, the document, the position of + | the current match, and the total list of matches. This allows you to + | write callbacks that consider the entire set of matched phrases, so that + | you can resolve overlaps and other conflicts in whatever way you prefer. + ++table(["Argument", "Type", "Description"]) + +row + +cell #[code matcher] + +cell #[code Matcher] + +cell The matcher instance. + + +row + +cell #[code doc] + +cell #[code Doc] + +cell The document the matcher was used on. + + +row + +cell #[code i] + +cell int + +cell Index of the current match (#[code matches[i]]). + + +row + +cell #[code matches] + +cell list + +cell + | A list of #[code (match_id, start, end)] tuples, describing the + | matches. A match tuple describes a span #[code doc[start:end]]. + ++h(2, "quantifiers") Using operators and quantifiers + +p + | The matcher also lets you use quantifiers, specified as the #[code 'OP'] + | key. Quantifiers let you define sequences of tokens to be mached, e.g. + | one or more punctuation marks, or specify optional tokens. Note that there + | are no nested or scoped quantifiers – instead, you can build those + | behaviours with #[code on_match] callbacks. + ++aside("Problems with quantifiers") + | Using quantifiers may lead to unexpected results when matching + | variable-length patterns, for example if the next token would also be + | matched by the previous token. This problem should be resolved in a future + | release. For more information, see + | #[+a(gh("spaCy") + "/issues/864") this issue]. + ++table([ "OP", "Description", "Example"]) +row +cell #[code !] +cell match exactly 0 times @@ -80,80 +221,212 @@ p +cell match 0 or 1 times +cell optional, max one -p - | There are no nested or scoped quantifiers. You can build those - | behaviours with acceptors and - | #[+api("matcher#add_entity") #[code on_match]] callbacks. - -+h(2, "acceptor-functions") Acceptor functions ++h(2, "example1") Example: Using linguistic annotations p - | The #[code acceptor] keyword of #[code matcher.add_entity()] allows you to - | pass a function to reject or modify matches. The function you pass should - | take five arguments: #[code doc], #[code ent_id], #[code label], #[code start], - | and #[code end]. You can return a falsey value to reject the match, or - | return a 4-tuple #[code (ent_id, label, start, end)]. + | Let's say you're analysing user comments and you want to find out what + | people are saying about Facebook. You want to start off by finding + | adjectives following "Facebook is" or "Facebook was". This is obviously + | a very rudimentary solution, but it'll be fast, and a great way get an + | idea for what's in your data. Your pattern could look like this: +code. - from spacy.tokens.doc import Doc - def trim_title(doc, ent_id, label, start, end): - if doc[start].check_flag(IS_TITLE_TERM): - return (ent_id, label, start+1, end) - else: - return (ent_id, label, start, end) - titles = set(title.lower() for title in [u'Mr.', 'Dr.', 'Ms.', u'Admiral']) - IS_TITLE_TERM = matcher.vocab.add_flag(lambda string: string.lower() in titles) - matcher.add_entity('PersonName', acceptor=trim_title) - matcher.add_pattern('PersonName', [{LOWER: 'mr.'}, {LOWER: 'cruise'}]) - matcher.add_pattern('PersonName', [{LOWER: 'dr.'}, {LOWER: 'seuss'}]) - doc = Doc(matcher.vocab, words=[u'Mr.', u'Cruise', u'likes', 'Dr.', u'Seuss']) - for ent_id, label, start, end in matcher(doc): - print(doc[start:end].text) - # Cruise - # Seuss + [{'LOWER': 'facebook'}, {'LEMMA': 'be'}, {'POS': 'ADV', 'OP': '*'}, {'POS': 'ADJ'}] p - | Passing an #[code acceptor] function allows you to match patterns with - | arbitrary logic that can't easily be expressed by a finite-state machine. - | You can look at the entirety of the - | matched phrase, and its context in the document, and decide to move - | the boundaries or reject the match entirely. - -+h(2, "callback-functions") Callback functions + | This translates to a token whose lowercase form matches "facebook" + | (like Facebook, facebook or FACEBOOK), followed by a token with the lemma + | "be" (for example, is, was, or 's), followed by an #[strong optional] adverb, + | followed by an adjective. Using the linguistic annotations here is + | especially useful, because you can tell spaCy to match "Facebook's + | annoying", but #[strong not] "Facebook's annoying ads". The optional + | adverb makes sure you won't miss adjectives with intensifiers, like + | "pretty awful" or "very nice". p - | In spaCy <1.0, the #[code Matcher] automatically tagged matched phrases - | with entity types. Since spaCy 1.0, the matcher no longer acts on matches - | automatically. By default, the match list is returned for the user to action. - | However, it's often more convenient to register the required actions as a - | callback. You can do this by passing a function to the #[code on_match] - | keyword argument of #[code matcher.add_entity]. + | To get a quick overview of the results, you could collect all sentences + | containing a match and render them with the + | #[+a("/docs/usage/visualizers") displaCy visualizer]. + | In the callback function, you'll have access to the #[code start] and + | #[code end] of each match, as well as the parent #[code Doc]. This lets + | you determine the sentence containing the match, + | #[code doc[start : end].sent], and calculate the start and end of the + | matched span within the sentence. Using displaCy in + | #[+a("/docs/usage/visualizers#manual-usage") "manual" mode] lets you + | pass in a list of dictionaries containing the text and entities to render. -+aside-code("Example"). - def merge_phrases(matcher, doc, i, matches): - ''' - Merge a phrase. We have to be careful here because we'll change the token indices. - To avoid problems, merge all the phrases once we're called on the last match. - ''' - if i != len(matches)-1: - return None - # Get Span objects - spans = [(ent_id, label, doc[start : end]) for ent_id, label, start, end in matches] - for ent_id, label, span in spans: - span.merge(label=label, tag='NNP' if label else span.root.tag_) ++code. + from spacy import displacy + from spacy.matcher import Matcher - matcher.add_entity('GoogleNow', on_match=merge_phrases) - matcher.add_pattern('GoogleNow', [{ORTH: 'Google'}, {ORTH: 'Now'}]) - doc = Doc(matcher.vocab, words=[u'Google', u'Now', u'is', u'being', u'rebranded']) - matcher(doc) - print([w.text for w in doc]) - # [u'Google Now', u'is', u'being', u'rebranded'] + nlp = spacy.load('en') + matcher = Matcher(nlp.vocab) + matched_sents = [] # collect data of matched sentences to be visualized + + def collect_sents(matcher, doc, i, matches): + match_id, start, end = matches[i] + span = doc[start : end] # matched span + sent = span.sent # sentence containing matched span + # append mock entity for match in displaCy style to matched_sents + # get the match span by ofsetting the start and end of the span with the + # start and end of the sentence in the doc + match_ents = [{'start': span.start-sent.start, 'end': span.end-sent.start, + 'label': 'MATCH'}] + matched_sents.append({'text': sent.text, 'ents': match_ents }) + + pattern = [{'LOWER': 'facebook'}, {'LEMMA': 'be'}, {'POS': 'ADV', 'OP': '*'}, + {'POS': 'ADJ'}] + matcher.add('FacebookIs', collect_sents, pattern) # add pattern + matches = matcher(nlp(LOTS_OF_TEXT)) # match on your text + + # serve visualization of sentences containing match with displaCy + # set manual=True to make displaCy render straight from a dictionary + displacy.serve(matched_sents, style='ent', manual=True) + ++h(2, "example2") Example: Phone numbers p - | The matcher will first collect all matches over the document. It will - | then iterate over the matches, look-up the callback for the entity ID - | that was matched, and invoke it. When the callback is invoked, it is - | passed four arguments: the matcher itself, the document, the position of - | the current match, and the total list of matches. This allows you to - | write callbacks that consider the entire set of matched phrases, so that - | you can resolve overlaps and other conflicts in whatever way you prefer. + | Phone numbers can have many different formats and matching them is often + | tricky. During tokenization, spaCy will leave sequences of numbers intact + | and only split on whitespace and punctuation. This means that your match + | pattern will have to look out for number sequences of a certain length, + | surrounded by specific punctuation – depending on the + | #[+a("https://en.wikipedia.org/wiki/National_conventions_for_writing_telephone_numbers") national conventions]. + +p + | The #[code IS_DIGIT] flag is not very helpful here, because it doesn't + | tell us anything about the length. However, you can use the #[code SHAPE] + | flag, with each #[code d] representing a digit: + ++code. + [{'ORTH': '('}, {'SHAPE': 'ddd'}, {'ORTH': ')'}, {'SHAPE': 'dddd'}, + {'ORTH': '-', 'OP': '?'}, {'SHAPE': 'dddd'}] + +p + | This will match phone numbers of the format #[strong (123) 4567 8901] or + | #[strong (123) 4567-8901]. To also match formats like #[strong (123) 456 789], + | you can add a second pattern using #[code 'ddd'] in place of #[code 'dddd']. + | By hard-coding some values, you can match only certain, country-specific + | numbers. For example, here's a pattern to match the most common formats of + | #[+a("https://en.wikipedia.org/wiki/National_conventions_for_writing_telephone_numbers#Germany") international German numbers]: + ++code. + [{'ORTH': '+'}, {'ORTH': '49'}, {'ORTH': '(', 'OP': '?'}, {'SHAPE': 'dddd'}, + {'ORTH': ')', 'OP': '?'}, {'SHAPE': 'dddddd'}] + +p + | Depending on the formats your application needs to match, creating an + | extensive set of rules like this is often better than training a model. + | It'll produce more predictable results, is much easier to modify and + | extend, and doesn't require any training data – only a set of + | test cases. + ++h(2, "example3") Example: Hashtags and emoji on social media + +p + | Social media posts, especially tweets, can be difficult to work with. + | They're very short and often contain various emoji and hashtags. By only + | looking at the plain text, you'll lose a lot of valuable semantic + | information. + +p + | Let's say you've extracted a large sample of social media posts on a + | specific topic, for example posts mentioning a brand name or product. + | As the first step of your data exploration, you want to filter out posts + | containing certain emoji and use them to assign a general sentiment + | score, based on whether the expressed emotion is positive or negative, + | e.g. #[span.o-icon.o-icon--inline 😀] or #[span.o-icon.o-icon--inline 😞]. + | You also want to find, merge and label hashtags like + | #[code #MondayMotivation], to be able to ignore or analyse them later. + ++aside("Note on sentiment analysis") + | Ultimately, sentiment analysis is not always #[em that] easy. In + | addition to the emoji, you'll also want to take specific words into + | account and check the #[code subtree] for intensifiers like "very", to + | increase the sentiment score. At some point, you might also want to train + | a sentiment model. However, the approach described in this example is + | very useful for #[strong bootstrapping rules to collect training data]. + | It's also an incredibly fast way to gather first insights into your data + | – with about 1 million tweets, you'd be looking at a processing time of + | #[strong under 1 minute]. + +p + | By default, spaCy's tokenizer will split emoji into separate tokens. This + | means that you can create a pattern for one or more emoji tokens. + | Valid hashtags usually consist of a #[code #], plus a sequence of + | ASCII characters with no whitespace, making them easy to match as well. + ++code. + from spacy.lang.en import English + from spacy.matcher import Matcher + + nlp = English() # we only want the tokenizer, so no need to load a model + matcher = Matcher(nlp.vocab) + + pos_emoji = [u'😀', u'😃', u'😂', u'🤣', u'😊', u'😍'] # positive emoji + neg_emoji = [u'😞', u'😠', u'😩', u'😢', u'😭', u'😒'] # negative emoji + + # add patterns to match one or more emoji tokens + pos_patterns = [[{'ORTH': emoji}] for emoji in pos_emoji] + neg_patterns = [[{'ORTH': emoji}] for emoji in neg_emoji] + + matcher.add('HAPPY', label_sentiment, *pos_patterns) # add positive pattern + matcher.add('SAD', label_sentiment, *neg_patterns) # add negative pattern + + # add pattern to merge valid hashtag, i.e. '#' plus any ASCII token + matcher.add('HASHTAG', merge_hashtag, [{'ORTH': '#'}, {'IS_ASCII': True}]) + +p + | Because the #[code on_match] callback receives the ID of each match, you + | can use the same function to handle the sentiment assignment for both + | the positive and negative pattern. To keep it simple, we'll either add + | or subtract #[code 0.1] points – this way, the score will also reflect + | combinations of emoji, even positive #[em and] negative ones. + +p + | With a library like + | #[+a("https://github.com/bcongdon/python-emojipedia") Emojipedia], + | we can also retrieve a short description for each emoji – for example, + | #[span.o-icon.o-icon--inline 😍]'s official title is "Smiling Face With + | Heart-Eyes". Assigning it to the merged token's norm will make it + | available as #[code token.norm_]. + ++code. + from emojipedia import Emojipedia # installation: pip install emojipedia + + def label_sentiment(matcher, doc, i, matches): + match_id, start, end = matches[i] + if doc.vocab.strings[match_id] == 'HAPPY': # don't forget to get string! + doc.sentiment += 0.1 # add 0.1 for positive sentiment + elif doc.vocab.strings[match_id] == 'SAD': + doc.sentiment -= 0.1 # subtract 0.1 for negative sentiment + span = doc[start : end] + emoji = Emojipedia.search(span[0].text) # get data for emoji + span.merge(norm=emoji.title) # merge span and set NORM to emoji title + +p + | To label the hashtags, we first need to add a new custom flag. + | #[code IS_HASHTAG] will be the flag's ID, which you can use to assign it + | to the hashtag's span, and check its value via a token's + | #[+api("token#check_flag") #[code check_flag()]] method. On each + | match, we merge the hashtag and assign the flag. + ++code. + # Add a new custom flag to the vocab, which is always False by default + IS_HASHTAG = nlp.vocab.add_flag(lambda text: False) + + def merge_hashtag(matcher, doc, i, matches): + match_id, start, end = matches[i] + span = doc[start : end] + span.merge() # merge hashtag + span.set_flag(IS_HASHTAG, True) # set IS_HASHTAG to True + +p + | To process a stream of social media posts, we can use + | #[+api("language#pipe") #[code Language.pipe()]], which will return a + | stream of #[code Doc] objects that we can pass to + | #[+api("matcher#pipe") #[code Matcher.pipe()]]. + ++code. + docs = nlp.pipe(LOTS_OF_TWEETS) + matches = matcher.pipe(docs) diff --git a/website/docs/usage/saving-loading.jade b/website/docs/usage/saving-loading.jade index c4eb08f04..827b54748 100644 --- a/website/docs/usage/saving-loading.jade +++ b/website/docs/usage/saving-loading.jade @@ -1,45 +1,87 @@ include ../../_includes/_mixins ++h(2, "101") Serialization 101 + +include _spacy-101/_serialization + ++infobox("Important note") + | In spaCy v2.0, the API for saving and loading has changed to only use the + | four methods listed above consistently across objects and classes. For an + | overview of the changes, see #[+a("/docs/usage/v2#incompat") this table] + | and the notes on #[+a("/docs/usage/v2#migrating-saving-loading") migrating]. + ++h(3, "example-doc") Example: Saving and loading a document + +p + | For simplicity, let's assume you've + | #[+a("/docs/usage/entity-recognition#setting") added custom entities] to + | a #[code Doc], either manually, or by using a + | #[+a("/docs/usage/rule-based-matching#on_match") match pattern]. You can + | save it locally by calling #[+api("doc#to_disk") #[code Doc.to_disk()]], + | and load it again via #[+api("doc#from_disk") #[code Doc.from_disk()]]. + | This will overwrite the existing object and return it. + ++code. + import spacy + from spacy.tokens import Span + + text = u'Netflix is hiring a new VP of global policy' + + nlp = spacy.load('en') + doc = nlp(text) + assert len(doc.ents) == 0 # Doc has no entities + doc.ents += ((Span(doc, 0, 1, label=doc.vocab.strings[u'ORG'])) # add entity + doc.to_disk('/path/to/doc') # save Doc to disk + + new_doc = nlp(text) + assert len(new_doc.ents) == 0 # new Doc has no entities + new_doc = new_doc.from_disk('path/to/doc') # load from disk and overwrite + assert len(new_doc.ents) == 1 # entity is now recognised! + assert [(ent.text, ent.label_) for ent in new_doc.ents] == [(u'Netflix', u'ORG')] + ++h(2, "models") Saving models + p | After training your model, you'll usually want to save its state, and load | it back later. You can do this with the - | #[+api("language#save_to_directory") #[code Language.save_to_directory()]] + | #[+api("language#to_disk") #[code Language.to_disk()]] | method: +code. - nlp.save_to_directory('/home/me/data/en_example_model') + nlp.to_disk('/home/me/data/en_example_model') p | The directory will be created if it doesn't exist, and the whole pipeline | will be written out. To make the model more convenient to deploy, we | recommend wrapping it as a Python package. -+h(2, "generating") Generating a model package ++h(3, "models-generating") Generating a model package +infobox("Important note") | The model packages are #[strong not suitable] for the public | #[+a("https://pypi.python.org") pypi.python.org] directory, which is not | designed for binary data and files over 50 MB. However, if your company - | is running an internal installation of pypi, publishing your models on - | there can be a convenient solution to share them with your team. + | is running an #[strong internal installation] of PyPi, publishing your + | models on there can be a convenient way to share them with your team. p | spaCy comes with a handy CLI command that will create all required files, | and walk you through generating the meta data. You can also create the | meta.json manually and place it in the model data directory, or supply a - | path to it using the #[code --meta] flag. For more info on this, see the - | #[+a("/docs/usage/cli/#package") #[code package] command] documentation. + | path to it using the #[code --meta] flag. For more info on this, see + | the #[+api("cli#package") #[code package]] docs. +aside-code("meta.json", "json"). { "name": "example_model", "lang": "en", "version": "1.0.0", - "spacy_version": ">=1.7.0,<2.0.0", + "spacy_version": ">=2.0.0,<3.0.0", "description": "Example model for spaCy", "author": "You", "email": "you@example.com", - "license": "CC BY-SA 3.0" + "license": "CC BY-SA 3.0", + "pipeline": ["token_vectors", "tagger"] } +code(false, "bash"). @@ -58,52 +100,112 @@ p This command will create a model package directory that should look like this: p | You can also find templates for all files in our - | #[+a(gh("spacy-dev-resouces", "templates/model")) spaCy dev resources]. + | #[+src(gh("spacy-dev-resources", "templates/model")) spaCy dev resources]. | If you're creating the package manually, keep in mind that the directories | need to be named according to the naming conventions of - | #[code [language]_[name]] and #[code [language]_[name]-[version]]. The - | #[code lang] setting in the meta.json is also used to create the - | respective #[code Language] class in spaCy, which will later be returned - | by the model's #[code load()] method. + | #[code lang_name] and #[code lang_name-version]. -+h(2, "building") Building a model package ++h(3, "models-custom") Customising the model setup + +p + | The meta.json includes the model details, like name, requirements and + | license, and lets you customise how the model should be initialised and + | loaded. You can define the language data to be loaded and the + | #[+a("/docs/usage/language-processing-pipeline") processing pipeline] to + | execute. + ++table(["Setting", "Type", "Description"]) + +row + +cell #[code lang] + +cell unicode + +cell ID of the language class to initialise. + + +row + +cell #[code pipeline] + +cell list + +cell + | A list of strings mapping to the IDs of pipeline factories to + | apply in that order. If not set, spaCy's + | #[+a("/docs/usage/language-processing/pipelines") default pipeline] + | will be used. + +p + | The #[code load()] method that comes with our model package + | templates will take care of putting all this together and returning a + | #[code Language] object with the loaded pipeline and data. If your model + | requires custom pipeline components, you should + | #[strong ship then with your model] and register their + | #[+a("/docs/usage/language-processing-pipeline#creating-factory") factories] + | via #[+api("spacy#set_factory") #[code set_factory()]]. + ++aside-code("Factory example"). + def my_factory(vocab): + # load some state + def my_component(doc): + # process the doc + return doc + return my_component + ++code. + spacy.set_factory('custom_component', custom_component_factory) + ++infobox("Custom models with pipeline components") + | For more details and an example of how to package a sentiment model + | with a custom pipeline component, see the usage guide on + | #[+a("/docs/usage/language-processing-pipeline#example2") language processing pipelines]. + ++h(3, "models-building") Building the model package p | To build the package, run the following command from within the - | directory. This will create a #[code .tar.gz] archive in a directory - | #[code /dist]. + | directory. For more information on building Python packages, see the + | docs on Python's + | #[+a("https://setuptools.readthedocs.io/en/latest/") Setuptools]. +code(false, "bash"). python setup.py sdist p - | For more information on building Python packages, see the - | #[+a("https://setuptools.readthedocs.io/en/latest/") Python Setuptools documentation]. - - -+h(2, "loading") Loading a model package - -p - | Model packages can be installed by pointing pip to the model's - | #[code .tar.gz] archive: + | This will create a #[code .tar.gz] archive in a directory #[code /dist]. + | The model can be installed by pointing pip to the path of the archive: +code(false, "bash"). pip install /path/to/en_example_model-1.0.0.tar.gz -p You'll then be able to load the model as follows: +p + | You can then load the model via its name, #[code en_example_model], or + | import it directly as a module and then call its #[code load()] method. -+code. - import en_example_model - nlp = en_example_model.load() ++h(2, "loading") Loading a custom model package p - | To load the model via #[code spacy.load()], you can also - | create a #[+a("/docs/usage/models#usage") shortcut link] that maps the - | package name to a custom model name of your choice: - -+code(false, "bash"). - python -m spacy link en_example_model example + | To load a model from a data directory, you can use + | #[+api("spacy#load") #[code spacy.load()]] with the local path. This will + | look for a meta.json in the directory and use the #[code lang] and + | #[code pipeline] settings to initialise a #[code Language] class with a + | processing pipeline and load in the model data. +code. - import spacy - nlp = spacy.load('example') + nlp = spacy.load('/path/to/model') + +p + | If you want to #[strong load only the binary data], you'll have to create + | a #[code Language] class and call + | #[+api("language#from_disk") #[code from_disk]] instead. + ++code. + from spacy.lang.en import English + nlp = English().from_disk('/path/to/data') + ++infobox("Important note: Loading data in v2.x") + .o-block + | In spaCy 1.x, the distinction between #[code spacy.load()] and the + | #[code Language] class constructor was quite unclear. You could call + | #[code spacy.load()] when no model was present, and it would silently + | return an empty object. Likewise, you could pass a path to + | #[code English], even if the mode required a different language. + | spaCy v2.0 solves this with a clear distinction between setting up + | the instance and loading the data. + + +code-new nlp = English().from_disk('/path/to/data') + +code-old nlp = spacy.load('en', path='/path/to/data') diff --git a/website/docs/usage/spacy-101.jade b/website/docs/usage/spacy-101.jade index daace114b..a54e5cf66 100644 --- a/website/docs/usage/spacy-101.jade +++ b/website/docs/usage/spacy-101.jade @@ -2,9 +2,429 @@ include ../../_includes/_mixins +p + | Whether you're new to spaCy, or just want to brush up on some + | NLP basics and implementation details – this page should have you covered. + | Each section will explain one of spaCy's features in simple terms and + | with examples or illustrations. Some sections will also reappear across + | the usage guides as a quick introcution. + ++aside("Help us improve the docs") + | Did you spot a mistake or come across explanations that + | are unclear? We always appreciate improvement + | #[+a(gh("spaCy") + "/issues") suggestions] or + | #[+a(gh("spaCy") + "/pulls") pull requests]. You can find a "Suggest + | edits" link at the bottom of each page that points you to the source. + ++h(2, "whats-spacy") What's spaCy? + ++grid.o-no-block + +grid-col("half") + p + | spaCy is a #[strong free, open-source library] for advanced + | #[strong Natural Language Processing] (NLP) in Python. + + p + | If you're working with a lot of text, you'll eventually want to + | know more about it. For example, what's it about? What do the + | words mean in context? Who is doing what to whom? What companies + | and products are mentioned? Which texts are similar to each other? + + p + | spaCy is designed specifically for #[strong production use] and + | helps you build applications that process and "understand" + | large volumes of text. It can be used to build + | #[strong information extraction] or + | #[strong natural language understanding] systems, or to + | pre-process text for #[strong deep learning]. + + +table-of-contents + +item #[+a("#features") Features] + +item #[+a("#annotations") Linguistic annotations] + +item #[+a("#annotations-token") Tokenization] + +item #[+a("#annotations-pos-deps") POS tags and dependencies] + +item #[+a("#annotations-ner") Named entities] + +item #[+a("#vectors-similarity") Word vectors and similarity] + +item #[+a("#pipelines") Pipelines] + +item #[+a("#vocab") Vocab, hashes and lexemes] + +item #[+a("#serialization") Serialization] + +item #[+a("#training") Training] + +item #[+a("#language-data") Language data] + +item #[+a("#architecture") Architecture] + +item #[+a("#community") Community & FAQ] + ++h(3, "what-spacy-isnt") What spaCy isn't + ++list + +item #[strong spaCy is not a platform or "an API"]. + | Unlike a platform, spaCy does not provide a software as a service, or + | a web application. It's an open-source library designed to help you + | build NLP applications, not a consumable service. + +item #[strong spaCy is not an out-of-the-box chat bot engine]. + | While spaCy can be used to power conversational applications, it's + | not designed specifically for chat bots, and only provides the + | underlying text processing capabilities. + +item #[strong spaCy is not research software]. + | It's is built on the latest research, but it's designed to get + | things done. This leads to fairly different design decisions than + | #[+a("https://github./nltk/nltk") NLTK] + | or #[+a("https://stanfordnlp.github.io/CoreNLP/") CoreNLP], which were + | created as platforms for teaching and research. The main difference + | is that spaCy is integrated and opinionated. spaCy tries to avoid asking + | the user to choose between multiple algorithms that deliver equivalent + | functionality. Keeping the menu small lets spaCy deliver generally better + | performance and developer experience. + +item #[strong spaCy is not a company]. + | It's an open-source library. Our company publishing spaCy and other + | software is called #[+a(COMPANY_URL, true) Explosion AI]. + ++h(2, "features") Features + +p + | In the documentation, you'll come across mentions of spaCy's + | features and capabilities. Some of them refer to linguistic concepts, + | while others are related to more general machine learning functionality. + ++aside + | If one of spaCy's functionalities #[strong needs a model], it means that + | you need to have one our the available + | #[+a("/docs/usage/models") statistical models] installed. Models are used + | to #[strong predict] linguistic annotations – for example, if a word is + | a verb or a noun. + ++table(["Name", "Description", "Needs model"]) + +row + +cell #[strong Tokenization] + +cell Segmenting text into words, punctuations marks etc. + +cell #[+procon("con")] + + +row + +cell #[strong Part-of-speech] (POS) #[strong Tagging] + +cell Assigning word types to tokens, like verb or noun. + +cell #[+procon("pro")] + + +row + +cell #[strong Dependency Parsing] + +cell + | Assigning syntactic dependency labels, describing the relations + | between individual tokens, like subject or object. + +cell #[+procon("pro")] + + +row + +cell #[strong Lemmatization] + +cell + | Assigning the base forms of words. For example, the lemma of + | "was" is "be", and the lemma of "rats" is "rat". + +cell #[+procon("pro")] + + +row + +cell #[strong Sentence Boundary Detection] (SBD) + +cell Finding and segmenting individual sentences. + +cell #[+procon("pro")] + + +row + +cell #[strong Named Entity Recongition] (NER) + +cell + | Labelling named "real-world" objects, like persons, companies or + | locations. + +cell #[+procon("pro")] + + +row + +cell #[strong Similarity] + +cell + | Comparing words, text spans and documents and how similar they + | are to each other. + +cell #[+procon("pro")] + + +row + +cell #[strong Text classification] + +cell Assigning categories or labels to a whole document, or parts of a document. + +cell #[+procon("pro")] + + +row + +cell #[strong Rule-based Matching] + +cell + | Finding sequences of tokens based on their texts and linguistic + | annotations, similar to regular expressions. + +cell #[+procon("con")] + + +row + +cell #[strong Training] + +cell Updating and improving a statistical model's predictions. + +cell #[+procon("neutral")] + + +row + +cell #[strong Serialization] + +cell Saving objects to files or byte strings. + +cell #[+procon("neutral")] + ++h(2, "annotations") Linguistic annotations + +p + | spaCy provides a variety of linguistic annotations to give you + | #[strong insights into a text's grammatical structure]. This includes the + | word types, like the parts of speech, and how the words are related to + | each other. For example, if you're analysing text, it makes a huge + | difference whether a noun is the subject of a sentence, or the object – + | or whether "google" is used as a verb, or refers to the website or + | company in a specific context. + +p + | Once you've downloaded and installed a #[+a("/docs/usage/models") model], + | you can load it via #[+api("spacy#load") #[code spacy.load()]]. This will + | return a #[code Language] object contaning all components and data needed + | to process text. We usually call it #[code nlp]. Calling the #[code nlp] + | object on a string of text will return a processed #[code Doc]: + ++code. + import spacy + + nlp = spacy.load('en') + doc = nlp(u'Apple is looking at buying U.K. startup for $1 billion') + +p + | Even though a #[code Doc] is processed – e.g. split into individual words + | and annotated – it still holds #[strong all information of the original text], + | like whitespace characters. You can always get the offset of a token into the + | original string, or reconstruct the original by joining the tokens and their + | trailing whitespace. This way, you'll never lose any information + | when processing text with spaCy. + ++h(3, "annotations-token") Tokenization + +include _spacy-101/_tokenization + ++infobox + | To learn more about how spaCy's tokenization rules work in detail, + | how to #[strong customise and replace] the default tokenizer and how to + | #[strong add language-specific data], see the usage guides on + | #[+a("/docs/usage/adding-languages") adding languages] and + | #[+a("/docs/usage/customizing-tokenizer") customising the tokenizer]. + ++h(3, "annotations-pos-deps") Part-of-speech tags and dependencies + +tag-model("dependency parse") + +include _spacy-101/_pos-deps + ++infobox + | To learn more about #[strong part-of-speech tagging] and rule-based + | morphology, and how to #[strong navigate and use the parse tree] + | effectively, see the usage guides on + | #[+a("/docs/usage/pos-tagging") part-of-speech tagging] and + | #[+a("/docs/usage/dependency-parse") using the dependency parse]. + ++h(3, "annotations-ner") Named Entities + +tag-model("named entities") + +include _spacy-101/_named-entities + ++infobox + | To learn more about entity recognition in spaCy, how to + | #[strong add your own entities] to a document and how to + | #[strong train and update] the entity predictions of a model, see the + | usage guides on + | #[+a("/docs/usage/entity-recognition") named entity recognition] and + | #[+a("/docs/usage/training-ner") training the named entity recognizer]. + ++h(2, "vectors-similarity") Word vectors and similarity + +tag-model("vectors") + +include _spacy-101/_similarity + +include _spacy-101/_word-vectors + ++infobox + | To learn more about word vectors, how to #[strong customise them] and + | how to load #[strong your own vectors] into spaCy, see the usage + | guide on + | #[+a("/docs/usage/word-vectors-similarities") using word vectors and semantic similarities]. + ++h(2, "pipelines") Pipelines + +include _spacy-101/_pipelines + ++infobox + | To learn more about #[strong how processing pipelines work] in detail, + | how to enable and disable their components, and how to + | #[strong create your own], see the usage guide on + | #[+a("/docs/usage/language-processing-pipeline") language processing pipelines]. + ++h(2, "vocab") Vocab, hashes and lexemes + +include _spacy-101/_vocab + ++h(2, "serialization") Serialization + +include _spacy-101/_serialization + ++infobox + | To learn more about #[strong serialization] and how to + | #[strong save and load your own models], see the usage guide on + | #[+a("/docs/usage/saving-loading") saving, loading and data serialization]. + ++h(2, "training") Training + +include _spacy-101/_training + ++infobox + | To learn more about #[strong training and updating] models, how to create + | training data and how to improve spaCy's named entity recognition models, + | see the usage guides on #[+a("/docs/usage/training") training] and + | #[+a("/docs/usage/training-ner") training the named entity recognizer]. + ++h(2, "language-data") Language data + +include _spacy-101/_language-data + ++infobox + | To learn more about the individual components of the language data and + | how to #[strong add a new language] to spaCy in preparation for training + | a language model, see the usage guide on + | #[+a("/docs/usage/adding-languages") adding languages]. + +h(2, "architecture") Architecture -+image - include ../../assets/img/docs/architecture.svg - .u-text-right - +button("/assets/img/docs/architecture.svg", false, "secondary").u-text-tag View large graphic +include _spacy-101/_architecture.jade + ++h(2, "community") Community & FAQ + +p + | We're very happy to see the spaCy community grow and include a mix of + | people from all kinds of different backgrounds – computational + | linguistics, data science, deep learning, research and more. If you'd + | like to get involved, below are some answers to the most important + | questions and resources for further reading. + ++h(3, "faq-help-code") Help, my code isn't working! + +p + | Bugs suck, and we're doing our best to continuously improve the tests + | and fix bugs as soon as possible. Before you submit an issue, do a + | quick search and check if the problem has already been reported. If + | you're having installation or loading problems, make sure to also check + | out the #[+a("/docs/usage#troubleshooting") troubleshooting guide]. Help + | with spaCy is available via the following platforms: + ++aside("How do I know if something is a bug?") + | Of course, it's always hard to know for sure, so don't worry – we're not + | going to be mad if a bug report turns out to be a typo in your + | code. As a simple rule, any C-level error without a Python traceback, + | like a #[strong segmentation fault] or #[strong memory error], + | is #[strong always] a spaCy bug.#[br]#[br] + + | Because models are statistical, their performance will never be + | #[em perfect]. However, if you come across + | #[strong patterns that might indicate an underlying issue], please do + | file a report. Similarly, we also care about behaviours that + | #[strong contradict our docs]. + ++table(["Platform", "Purpose"]) + +row + +cell #[+a("https://stackoverflow.com/questions/tagged/spacy") StackOverflow] + +cell + | #[strong Usage questions] and everything related to problems with + | your specific code. The StackOverflow community is much larger + | than ours, so if your problem can be solved by others, you'll + | receive help much quicker. + + +row + +cell #[+a("https://gitter.im/" + SOCIAL.gitter) Gitter chat] + +cell + | #[strong General discussion] about spaCy, meeting other community + | members and exchanging #[strong tips, tricks and best practices]. + | If we're working on experimental models and features, we usually + | share them on Gitter first. + + +row + +cell #[+a(gh("spaCy") + "/issues") GitHub issue tracker] + +cell + | #[strong Bug reports] and #[strong improvement suggestions], i.e. + | everything that's likely spaCy's fault. This also includes + | problems with the models beyond statistical imprecisions, like + | patterns that point to a bug. + ++infobox + | Please understand that we won't be able to provide individual support via + | email. We also believe that help is much more valuable if it's shared + | publicly, so that #[strong more people can benefit from it]. If you come + | across an issue and you think you might be able to help, consider posting + | a quick update with your solution. No matter how simple, it can easily + | save someone a lot of time and headache – and the next time you need help, + | they might repay the favour. + ++h(3, "faq-contributing") How can I contribute to spaCy? + +p + | You don't have to be an NLP expert or Python pro to contribute, and we're + | happy to help you get started. If you're new to spaCy, a good place to + | start is the + | #[+a(gh("spaCy") + '/issues?q=is%3Aissue+is%3Aopen+label%3A"help+wanted+%28easy%29"') #[code help wanted (easy)] label] + | on GitHub, which we use to tag bugs and feature requests that are easy + | and self-contained. We also appreciate contributions to the docs – whether + | it's fixing a typo, improving an example or adding additional explanations. + | You'll find a "Suggest edits" link at the bottom of each page that points + | you to the source. + +p + | Another way of getting involved is to help us improve the + | #[+a("/docs/usage/adding-languages#language-data") language data] – + | especially if you happen to speak one of the languages currently in + | #[+a("/docs/api/language-models#alpha-support") alpha support]. Even + | adding simple tokenizer exceptions, stop words or lemmatizer data + | can make a big difference. It will also make it easier for us to provide + | a statistical model for the language in the future. Submitting a test + | that documents a bug or performance issue, or covers functionality that's + | especially important for your application is also very helpful. This way, + | you'll also make sure we never accidentally introduce regressions to the + | parts of the library that you care about the most. + +p + strong + | For more details on the types of contributions we're looking for, the + | code conventions and other useful tips, make sure to check out the + | #[+a(gh("spaCy", "CONTRIBUTING.md")) contributing guidelines]. + ++infobox("Code of Conduct") + | spaCy adheres to the + | #[+a("http://contributor-covenant.org/version/1/4/") Contributor Covenant Code of Conduct]. + | By participating, you are expected to uphold this code. + ++h(3, "faq-project-with-spacy") + | I've built something cool with spaCy – how can I get the word out? + +p + | First, congrats – we'd love to check it out! When you share your + | project on Twitter, don't forget to tag + | #[+a("https://twitter.com/" + SOCIAL.twitter) @#{SOCIAL.twitter}] so we + | don't miss it. If you think your project would be a good fit for the + | #[+a("/docs/usage/showcase") showcase], #[strong feel free to submit it!] + | Tutorials are also incredibly valuable to other users and a great way to + | get exposure. So we strongly encourage #[strong writing up your experiences], + | or sharing your code and some tips and tricks on your blog. Since our + | website is open-source, you can add your project or tutorial by making a + | pull request on GitHub. + ++aside("Contributing to spacy.io") + | All showcase and tutorial links are stored in a + | #[+a(gh("spaCy", "website/docs/usage/_data.json")) JSON file], so you + | won't even have to edit any markup. For more info on how to submit + | your project, see the + | #[+a(gh("spaCy", "CONTRIBUTING.md#submitting-a-project-to-the-showcase")) contributing guidelines] + | and our #[+a(gh("spaCy", "website")) website docs]. + +p + | If you would like to use the spaCy logo on your site, please get in touch + | and ask us first. However, if you want to show support and tell others + | that your project is using spaCy, you can grab one of our + | #[strong spaCy badges] here: + +- SPACY_BADGES = ["built%20with-spaCy-09a3d5.svg", "made%20with%20❤%20and-spaCy-09a3d5.svg", "spaCy-v2-09a3d5.svg"] ++quickstart([{id: "badge", input_style: "check", options: SPACY_BADGES.map(function(badge, i) { return {id: i, title: "", checked: (i == 0) ? true : false}}) }], false, false, true) + .c-code-block(data-qs-results) + for badge, i in SPACY_BADGES + - var url = "https://img.shields.io/badge/" + badge + +code(false, "text", "star").o-no-block(data-qs-badge=i)=url + +code(false, "text", "code").o-no-block(data-qs-badge=i). + <a href="#{SITE_URL}"><img src="#{url}" height="20"></a> + +code(false, "text", "markdown").o-no-block(data-qs-badge=i). + [![spaCy](#{url})](#{SITE_URL}) diff --git a/website/docs/usage/text-classification.jade b/website/docs/usage/text-classification.jade new file mode 100644 index 000000000..33e384dbd --- /dev/null +++ b/website/docs/usage/text-classification.jade @@ -0,0 +1,5 @@ +//- 💫 DOCS > USAGE > TEXT CLASSIFICATION + +include ../../_includes/_mixins + ++under-construction diff --git a/website/docs/usage/training-ner.jade b/website/docs/usage/training-ner.jade index 78eb4905e..3d732b16d 100644 --- a/website/docs/usage/training-ner.jade +++ b/website/docs/usage/training-ner.jade @@ -12,25 +12,19 @@ p p | To update the model, you first need to create an instance of - | #[+api("goldparse") #[code spacy.gold.GoldParse]], with the entity labels - | you want to learn. You will then pass this instance to the - | #[+api("entityrecognizer#update") #[code EntityRecognizer.update()]] - | method. For example: + | #[+api("goldparse") #[code GoldParse]], with the entity labels + | you want to learn. You'll usually need to provide many examples to + | meaningfully improve the system — a few hundred is a good start, although + | more is better. -+code. - import spacy - from spacy.gold import GoldParse - - nlp = spacy.load('en') - doc = nlp.make_doc(u'Facebook released React in 2014') - gold = GoldParse(doc, entities=['U-ORG', 'O', 'U-TECHNOLOGY', 'O', 'U-DATE']) - nlp.entity.update(doc, gold) ++image + include ../../assets/img/docs/training-loop.svg + .u-text-right + +button("/assets/img/docs/training-loop.svg", false, "secondary").u-text-tag View large graphic p - | You'll usually need to provide many examples to meaningfully improve the - | system — a few hundred is a good start, although more is better. You - | should avoid iterating over the same few examples multiple times, or the - | model is likely to "forget" how to annotate other examples. If you + | You should avoid iterating over the same few examples multiple times, or + | the model is likely to "forget" how to annotate other examples. If you | iterate over the same few examples, you're effectively changing the loss | function. The optimizer will find a way to minimize the loss on your | examples, without regard for the consequences on the examples it's no @@ -44,41 +38,68 @@ p | #[strong experiment on your own data] to find a solution that works best | for you. -+h(2, "adding") Adding a new entity type ++h(2, "example") Example -p - | You can add new entity types to an existing model. Let's say we want to - | recognise the category #[code TECHNOLOGY]. The new category will include - | programming languages, frameworks and platforms. First, we need to - | register the new entity type: ++under-construction +code. - nlp.entity.add_label('TECHNOLOGY') + import random + from spacy.lang.en import English + from spacy.gold import GoldParse, biluo_tags_from_offsets -p - | Next, iterate over your examples, calling #[code entity.update()]. As - | above, we want to avoid iterating over only a small number of sentences. - | A useful compromise is to run the model over a number of plain-text - | sentences, and pass the entities to #[code GoldParse], as "true" - | annotations. This encourages the optimizer to find a solution that - | predicts the new category with minimal difference from the previous - | output. + def main(model_dir=None): + train_data = [ + ('Who is Shaka Khan?', + [(len('Who is '), len('Who is Shaka Khan'), 'PERSON')]), + ('I like London and Berlin.', + [(len('I like '), len('I like London'), 'LOC'), + (len('I like London and '), len('I like London and Berlin'), 'LOC')]) + ] + nlp = English(pipeline=['tensorizer', 'ner']) + get_data = lambda: reformat_train_data(nlp.tokenizer, train_data) + optimizer = nlp.begin_training(get_data) + for itn in range(100): + random.shuffle(train_data) + losses = {} + for raw_text, entity_offsets in train_data: + doc = nlp.make_doc(raw_text) + gold = GoldParse(doc, entities=entity_offsets) + nlp.update([doc], [gold], drop=0.5, sgd=optimizer, losses=losses) + nlp.to_disk(model_dir) + ++code. + def reformat_train_data(tokenizer, examples): + """Reformat data to match JSON format""" + output = [] + for i, (text, entity_offsets) in enumerate(examples): + doc = tokenizer(text) + ner_tags = biluo_tags_from_offsets(tokenizer(text), entity_offsets) + words = [w.text for w in doc] + tags = ['-'] * len(doc) + heads = [0] * len(doc) + deps = [''] * len(doc) + sentence = (range(len(doc)), words, tags, heads, deps, ner_tags) + output.append((text, [(sentence, [])])) + return output + +p.u-text-right + +button(gh("spaCy", "examples/training/train_ner.py"), false, "secondary").u-text-tag View full example +h(2, "saving-loading") Saving and loading p | After training our model, you'll usually want to save its state, and load - | it back later. You can do this with the #[code Language.save_to_directory()] - | method: + | it back later. You can do this with the + | #[+api("language#to_disk") #[code Language.to_disk()]] method: +code. - nlp.save_to_directory('/home/me/data/en_technology') + nlp.to_disk('/home/me/data/en_technology') p | To make the model more convenient to deploy, we recommend wrapping it as | a Python package, so that you can install it via pip and load it as a - | module. spaCy comes with a handy #[+a("/docs/usage/cli#package") CLI command] - | to create all required files and directories. + | module. spaCy comes with a handy #[+api("cli#package") #[code package]] + | CLI command to create all required files and directories. +code(false, "bash"). python -m spacy package /home/me/data/en_technology /home/me/my_models @@ -90,85 +111,4 @@ p +infobox("Saving and loading models") | For more information and a detailed guide on how to package your model, | see the documentation on - | #[+a("/docs/usage/saving-loading") saving and loading models]. - -p - | After you've generated and installed the package, you'll be able to - | load the model as follows: - -+code. - import en_technology - nlp = en_technology.load() - -+h(2, "example") Example: Adding and training an #[code ANIMAL] entity - -p - | This script shows how to add a new entity type to an existing pre-trained - | NER model. To keep the example short and simple, only four sentences are - | provided as examples. In practice, you'll need many more — - | #[strong a few hundred] would be a good start. You will also likely need - | to mix in #[strong examples of other entity types], which might be - | obtained by running the entity recognizer over unlabelled sentences, and - | adding their annotations to the training set. - -p - | For the full, runnable script of this example, see - | #[+src(gh("spacy", "examples/training/train_new_entity_type.py")) train_new_entity_type.py]. - -+code("Training the entity recognizer"). - import spacy - from spacy.pipeline import EntityRecognizer - from spacy.gold import GoldParse - from spacy.tagger import Tagger - import random - - model_name = 'en' - entity_label = 'ANIMAL' - output_directory = '/path/to/model' - train_data = [ - ("Horses are too tall and they pretend to care about your feelings", - [(0, 6, 'ANIMAL')]), - ("horses are too tall and they pretend to care about your feelings", - [(0, 6, 'ANIMAL')]), - ("horses pretend to care about your feelings", - [(0, 6, 'ANIMAL')]), - ("they pretend to care about your feelings, those horses", - [(48, 54, 'ANIMAL')]) - ] - - nlp = spacy.load(model_name) - nlp.entity.add_label(entity_label) - ner = train_ner(nlp, train_data, output_directory) - - def train_ner(nlp, train_data, output_dir): - # Add new words to vocab - for raw_text, _ in train_data: - doc = nlp.make_doc(raw_text) - for word in doc: - _ = nlp.vocab[word.orth] - - for itn in range(20): - random.shuffle(train_data) - for raw_text, entity_offsets in train_data: - gold = GoldParse(doc, entities=entity_offsets) - doc = nlp.make_doc(raw_text) - nlp.tagger(doc) - loss = nlp.entity.update(doc, gold) - nlp.end_training() - nlp.save_to_directory(output_dir) - -p - +button(gh("spaCy", "examples/training/train_new_entity_type.py"), false, "secondary") Full example - -p - | The actual training is performed by looping over the examples, and - | calling #[code nlp.entity.update()]. The #[code update()] method steps - | through the words of the input. At each word, it makes a prediction. It - | then consults the annotations provided on the #[code GoldParse] instance, - | to see whether it was right. If it was wrong, it adjusts its weights so - | that the correct action will score higher next time. - -p - | After training your model, you can - | #[+a("/docs/usage/saving-loading") save it to a directory]. We recommend wrapping - | models as Python packages, for ease of deployment. + | #[+a("/docs/usage/saving-loading#models") saving and loading models]. diff --git a/website/docs/usage/training.jade b/website/docs/usage/training.jade index 8a5c111bd..c1a7c1835 100644 --- a/website/docs/usage/training.jade +++ b/website/docs/usage/training.jade @@ -1,135 +1,202 @@ include ../../_includes/_mixins p - | This workflow describes how to train new statistical models for spaCy's + | This guide describes how to train new statistical models for spaCy's | part-of-speech tagger, named entity recognizer and dependency parser. | Once the model is trained, you can then | #[+a("/docs/usage/saving-loading") save and load] it. -+h(2, "train-pos-tagger") Training the part-of-speech tagger ++h(2, "101") Training 101 + +include _spacy-101/_training + ++h(3, "training-data") How do I get training data? + +p + | Collecting training data may sound incredibly painful – and it can be, + | if you're planning a large-scale annotation project. However, if your main + | goal is to update an existing model's predictions – for example, spaCy's + | named entity recognition – the hard is part usually not creating the + | actual annotations. It's finding representative examples and + | #[strong extracting potential candidates]. The good news is, if you've + | been noticing bad performance on your data, you likely + | already have some relevant text, and you can use spaCy to + | #[strong bootstrap a first set of training examples]. For example, + | after processing a few sentences, you may end up with the following + | entities, some correct, some incorrect. + ++aside("How many examples do I need?") + | As a rule of thumb, you should allocate at least 10% of your project + | resources to creating training and evaluation data. If you're looking to + | improve an existing model, you might be able to start off with only a + | handful of examples. Keep in mind that you'll always want a lot more than + | that for #[strong evaluation] – especially previous errors the model has + | made. Otherwise, you won't be able to sufficiently verify that the model + | has actually made the #[strong correct generalisations] required for your + | use case. + ++table(["Text", "Entity", "Start", "End", "Label", ""]) + - var style = [0, 0, 1, 1, 1] + +annotation-row(["Uber blew through $1 million a week", "Uber", 0, 4, "ORG"], style) + +cell #[+procon("pro")] + +annotation-row(["Android Pay expands to Canada", "Android", 0, 7, "PERSON"], style) + +cell #[+procon("con")] + +annotation-row(["Android Pay expands to Canada", "Canada", 23, 30, "GPE"], style) + +cell #[+procon("pro")] + +annotation-row(["Spotify steps up Asia expansion", "Spotify", 0, 8, "ORG"], style) + +cell #[+procon("pro")] + +annotation-row(["Spotify steps up Asia expansion", "Asia", 17, 21, "NORP"], style) + +cell #[+procon("con")] + +p + | Alternatively, the + | #[+a("/docs/usage/rule-based-matching#example3") rule-based matcher] + | can be a useful tool to extract tokens or combinations of tokens, as + | well as their start and end index in a document. In this case, we'll + | extract mentions of Google and assume they're an #[code ORG]. + ++table(["Text", "Entity", "Start", "End", "Label", ""]) + - var style = [0, 0, 1, 1, 1] + +annotation-row(["let me google this for you", "google", 7, 13, "ORG"], style) + +cell #[+procon("con")] + +annotation-row(["Google Maps launches location sharing", "Google", 0, 6, "ORG"], style) + +cell #[+procon("con")] + +annotation-row(["Google rebrands its business apps", "Google", 0, 6, "ORG"], style) + +cell #[+procon("pro")] + +annotation-row(["look what i found on google! 😂", "google", 21, 27, "ORG"], style) + +cell #[+procon("con")] + +p + | Based on the few examples above, you can already create six training + | sentences with eight entities in total. Of course, what you consider a + | "correct annotation" will always depend on + | #[strong what you want the model to learn]. While there are some entity + | annotations that are more or less universally correct – like Canada being + | a geopolitical entity – your application may have its very own definition + | of the #[+a("/docs/api/annotation#named-entities") NER annotation scheme]. +code. - from spacy.vocab import Vocab - from spacy.tagger import Tagger - from spacy.tokens import Doc - from spacy.gold import GoldParse + train_data = [ + ("Uber blew through $1 million a week", [(0, 4, 'ORG')]), + ("Android Pay expands to Canada", [(0, 11, 'PRODUCT'), (23, 30, 'GPE')]), + ("Spotify steps up Asia expansion", [(0, 8, "ORG"), (17, 21, "LOC")]), + ("Google Maps launches location sharing", [(0, 11, "PRODUCT")]), + ("Google rebrands its business apps", [(0, 6, "ORG")]), + ("look what i found on google! 😂", [(21, 27, "PRODUCT")])] ++h(2) Training with annotations +p + | The #[+api("goldparse") #[code GoldParse]] object collects the annotated + | training examples, also called the #[strong gold standard]. It's + | initialised with the #[+api("doc") #[code Doc]] object it refers to, + | and keyword arguments specifying the annotations, like #[code tags] + | or #[code entities]. Its job is to encode the annotations, keep them + | aligned and create the C-level data structures required for efficient access. + | Here's an example of a simple #[code GoldParse] for part-of-speech tags: + ++code. vocab = Vocab(tag_map={'N': {'pos': 'NOUN'}, 'V': {'pos': 'VERB'}}) - tagger = Tagger(vocab) - doc = Doc(vocab, words=['I', 'like', 'stuff']) gold = GoldParse(doc, tags=['N', 'V', 'N']) - tagger.update(doc, gold) - - tagger.model.end_training() p - +button(gh("spaCy", "examples/training/train_tagger.py"), false, "secondary") Full example - -+h(2, "train-entity") Training the named entity recognizer + | Using the #[code Doc] and its gold-standard annotations, the model can be + | updated to learn a sentence of three words with their assigned + | part-of-speech tags. The #[+a("/docs/usage/adding-languages#tag-map") tag map] + | is part of the vocabulary and defines the annotation scheme. If you're + | training a new language model, this will let you map the tags present in + | the treebank you train on to spaCy's tag scheme. +code. - from spacy.vocab import Vocab - from spacy.pipeline import EntityRecognizer - from spacy.tokens import Doc - - vocab = Vocab() - entity = EntityRecognizer(vocab, entity_types=['PERSON', 'LOC']) - - doc = Doc(vocab, words=['Who', 'is', 'Shaka', 'Khan', '?']) - entity.update(doc, ['O', 'O', 'B-PERSON', 'L-PERSON', 'O']) - - entity.model.end_training() + doc = Doc(Vocab(), words=['Facebook', 'released', 'React', 'in', '2014']) + gold = GoldParse(doc, entities=['U-ORG', 'O', 'U-TECHNOLOGY', 'O', 'U-DATE']) p - +button(gh("spaCy", "examples/training/train_ner.py"), false, "secondary") Full example + | The same goes for named entities. The letters added before the labels + | refer to the tags of the + | #[+a("/docs/usage/entity-recognition#updating-biluo") BILUO scheme] – + | #[code O] is a token outside an entity, #[code U] an single entity unit, + | #[code B] the beginning of an entity, #[code I] a token inside an entity + | and #[code L] the last token of an entity. -+h(2, "extend-entity") Extending the named entity recognizer ++aside + | #[strong Training data]: The training examples.#[br] + | #[strong Text and label]: The current example.#[br] + | #[strong Doc]: A #[code Doc] object created from the example text.#[br] + | #[strong GoldParse]: A #[code GoldParse] object of the #[code Doc] and label.#[br] + | #[strong nlp]: The #[code nlp] object with the model.#[br] + | #[strong Optimizer]: A function that holds state between updates.#[br] + | #[strong Update]: Update the model's weights.#[br] + | #[strong ] + ++image + include ../../assets/img/docs/training-loop.svg + .u-text-right + +button("/assets/img/docs/training-loop.svg", false, "secondary").u-text-tag View large graphic p - | All #[+a("/docs/usage/models") spaCy models] support online learning, so - | you can update a pre-trained model with new examples. You can even add - | new classes to an existing model, to recognise a new entity type, - | part-of-speech, or syntactic relation. Updating an existing model is - | particularly useful as a "quick and dirty solution", if you have only a - | few corrections or annotations. + | Of course, it's not enough to only show a model a single example once. + | Especially if you only have few examples, you'll want to train for a + | #[strong number of iterations]. At each iteration, the training data is + | #[strong shuffled] to ensure the model doesn't make any generalisations + | based on the order of examples. Another technique to improve the learning + | results is to set a #[strong dropout rate], a rate at which to randomly + | "drop" individual features and representations. This makes it harder for + | the model to memorise the training data. For example, a #[code 0.25] + | dropout means that each feature or internal representation has a 1/4 + | likelihood of being dropped. -p.o-inline-list - +button(gh("spaCy", "examples/training/train_new_entity_type.py"), true, "secondary") Full example - +button("/docs/usage/training-ner", false, "secondary") Usage Workflow ++aside + | #[+api("language#begin_training") #[code begin_training()]]: Start the + | training and return an optimizer function to update the model's weights.#[br] + | #[+api("language#update") #[code update()]]: Update the model with the + | training example and gold data.#[br] + | #[+api("language#to_disk") #[code to_disk()]]: Save the updated model to + | a directory. -+h(2, "train-dependency") Training the dependency parser ++code("Example training loop"). + optimizer = nlp.begin_training(get_data) + for itn in range(100): + random.shuffle(train_data) + for raw_text, entity_offsets in train_data: + doc = nlp.make_doc(raw_text) + gold = GoldParse(doc, entities=entity_offsets) + nlp.update([doc], [gold], drop=0.5, sgd=optimizer) + nlp.to_disk('/model') -+code. - from spacy.vocab import Vocab - from spacy.pipeline import DependencyParser - from spacy.tokens import Doc ++table(["Name", "Description"]) + +row + +cell #[code train_data] + +cell The training data. - vocab = Vocab() - parser = DependencyParser(vocab, labels=['nsubj', 'compound', 'dobj', 'punct']) + +row + +cell #[code get_data] + +cell A function converting the training data to spaCy's JSON format. - doc = Doc(vocab, words=['Who', 'is', 'Shaka', 'Khan', '?']) - parser.update(doc, [(1, 'nsubj'), (1, 'ROOT'), (3, 'compound'), (1, 'dobj'), - (1, 'punct')]) + +row + +cell #[code doc] + +cell #[+api("doc") #[code Doc]] objects. - parser.model.end_training() + +row + +cell #[code gold] + +cell #[+api("goldparse") #[code GoldParse]] objects. -p - +button(gh("spaCy", "examples/training/train_parser.py"), false, "secondary") Full example + +row + +cell #[code drop] + +cell Dropout rate. Makes it harder for the model to just memorise the data. -+h(2, "feature-templates") Customizing the feature extraction + +row + +cell #[code optimizer] + +cell Callable to update the model's weights. -p - | spaCy currently uses linear models for the tagger, parser and entity - | recognizer, with weights learned using the - | #[+a("https://explosion.ai/blog/part-of-speech-pos-tagger-in-python") Averaged Perceptron algorithm]. ++infobox + | For the #[strong full example and more details], see the usage guide on + | #[+a("/docs/usage/training-ner") training the named entity recognizer], + | or the runnable + | #[+src(gh("spaCy", "examples/training/train_ner.py")) training script] + | on GitHub. -+aside("Linear Model Feature Scheme") - | For a list of the available feature atoms, see the #[+a("/docs/api/features") Linear Model Feature Scheme]. ++h(2) Examples -p - | Because it's a linear model, it's important for accuracy to build - | conjunction features out of the atomic predictors. Let's say you have - | two atomic predictors asking, "What is the part-of-speech of the - | previous token?", and "What is the part-of-speech of the previous - | previous token?". These predictors will introduce a number of features, - | e.g. #[code Prev-pos=NN], #[code Prev-pos=VBZ], etc. A conjunction - | template introduces features such as #[code Prev-pos=NN&Prev-pos=VBZ]. - -p - | The feature extraction proceeds in two passes. In the first pass, we - | fill an array with the values of all of the atomic predictors. In the - | second pass, we iterate over the feature templates, and fill a small - | temporary array with the predictors that will be combined into a - | conjunction feature. Finally, we hash this array into a 64-bit integer, - | using the MurmurHash algorithm. You can see this at work in the - | #[+a(gh("thinc", "thinc/linear/features.pyx", "94dbe06fd3c8f24d86ab0f5c7984e52dbfcdc6cb")) #[code thinc.linear.features]] module. - -p - | It's very easy to change the feature templates, to create novel - | combinations of the existing atomic predictors. There's currently no API - | available to add new atomic predictors, though. You'll have to create a - | subclass of the model, and write your own #[code set_featuresC] method. - -p - | The feature templates are passed in using the #[code features] keyword - | argument to the constructors of the #[+api("tagger") #[code Tagger]], - | #[+api("dependencyparser") #[code DependencyParser]] and - | #[+api("entityrecognizer") #[code EntityRecognizer]]: - -+code. - from spacy.vocab import Vocab - from spacy.pipeline import Tagger - from spacy.tagger import P2_orth, P1_orth - from spacy.tagger import P2_cluster, P1_cluster, W_orth, N1_orth, N2_orth - - vocab = Vocab(tag_map={'N': {'pos': 'NOUN'}, 'V': {'pos': 'VERB'}}) - tagger = Tagger(vocab, features=[(P2_orth, P2_cluster), (P1_orth, P1_cluster), - (P2_orth,), (P1_orth,), (W_orth,), - (N1_orth,), (N2_orth,)]) - -p - | Custom feature templates can be passed to the #[code DependencyParser] - | and #[code EntityRecognizer] as well, also using the #[code features] - | keyword argument of the constructor. ++under-construction diff --git a/website/docs/usage/troubleshooting.jade b/website/docs/usage/troubleshooting.jade deleted file mode 100644 index 501a250c8..000000000 --- a/website/docs/usage/troubleshooting.jade +++ /dev/null @@ -1,190 +0,0 @@ -//- 💫 DOCS > USAGE > TROUBLESHOOTING - -include ../../_includes/_mixins - -p - | This section collects some of the most common errors you may come - | across when installing, loading and using spaCy, as well as their solutions. - -+aside("Help us improve this guide") - | Did you come across a problem like the ones listed here and want to - | share the solution? You can find the "Suggest edits" button at the - | bottom of this page that points you to the source. We always - | appreciate #[+a(gh("spaCy") + "/pulls") pull requests]! - -+h(2, "install-loading") Installation and loading - -+h(3, "compatible-model") No compatible model found - -+code(false, "text"). - No compatible model found for [lang] (spaCy v#{SPACY_VERSION}). - -p - | This usually means that the model you're trying to download does not - | exist, or isn't available for your version of spaCy. - -+infobox("Solutions") - | Check the #[+a(gh("spacy-models", "compatibility.json")) compatibility table] - | to see which models are available for your spaCy version. If you're using - | an old version, consider upgrading to the latest release. Note that while - | spaCy supports tokenization for - | #[+a("/docs/api/language-models/#alpha-support") a variety of languages], - | not all of them come with statistical models. To only use the tokenizer, - | import the language's #[code Language] class instead, for example - | #[code from spacy.fr import French]. - -+h(3, "symlink-privilege") Symbolic link privilege not held - -+code(false, "text"). - OSError: symbolic link privilege not held - -p - | To create #[+a("/docs/usage/models/#usage") shortcut links] that let you - | load models by name, spaCy creates a symbolic link in the - | #[code spacy/data] directory. This means your user needs permission to do - | this. The above error mostly occurs when doing a system-wide installation, - | which will create the symlinks in a system directory. - -+infobox("Solutions") - | Run the #[code download] or #[code link] command as administrator, - | or use a #[code virtualenv] to install spaCy in a user directory, instead - | of doing a system-wide installation. - -+h(3, "no-cache-dir") No such option: --no-cache-dir - -+code(false, "text"). - no such option: --no-cache-dir - -p - | The #[code download] command uses pip to install the models and sets the - | #[code --no-cache-dir] flag to prevent it from requiring too much memory. - | #[+a("https://pip.pypa.io/en/stable/reference/pip_install/#caching") This setting] - | requires pip v6.0 or newer. - -+infobox("Solution") - | Run #[code pip install -U pip] to upgrade to the latest version of pip. - | To see which version you have installed, run #[code pip --version]. - -+h(3, "import-error") Import error - -+code(false, "text"). - Import Error: No module named spacy - -p - | This error means that the spaCy module can't be located on your system, or in - | your environment. - -+infobox("Solutions") - | Make sure you have spaCy installed. If you're using a #[code virtualenv], - | make sure it's activated and check that spaCy is installed in that - | environment – otherwise, you're trying to load a system installation. You - | can also run #[code which python] to find out where your Python - | executable is located. - -+h(3, "import-error-models") Import error: models - -+code(false, "text"). - ImportError: No module named 'en_core_web_sm' - -p - | As of spaCy v1.7, all models can be installed as Python packages. This means - | that they'll become importable modules of your application. When creating - | #[+a("/docs/usage/models/#usage") shortcut links], spaCy will also try - | to import the model to load its meta data. If this fails, it's usually a - | sign that the package is not installed in the current environment. - -+infobox("Solutions") - | Run #[code pip list] or #[code pip freeze] to check which model packages - | you have installed, and install the - | #[+a("/docs/usage/models#available") correct models] if necessary. If you're - | importing a model manually at the top of a file, make sure to use the name - | of the package, not the shortcut link you've created. - -+h(3, "vocab-strings") File not found: vocab/strings.json - -+code(false, "text"). - FileNotFoundError: No such file or directory: [...]/vocab/strings.json - -p - | This error may occur when using #[code spacy.load()] to load - | a language model – either because you haven't set up a - | #[+a("/docs/usage/models/#usage") shortcut link] for it, or because it - | doesn't actually exist. - -+infobox("Solutions") - | Set up a #[+a("/docs/usage/models/#usage") shortcut link] for the model - | you want to load. This can either be an installed model package, or a - | local directory containing the model data. If you want to use one of the - | #[+a("/docs/api/language-models/#alpha-support") alpha tokenizers] for - | languages that don't yet have a statistical model, you should import its - | #[code Language] class instead, for example - | #[code from spacy.fr import French]. - -+h(3, "command-not-found") Command not found - -+code(false, "text"). - command not found: spacy - -p - | This error may occur when running the #[code spacy] command from the - | command line. spaCy does not currently add an entry to our #[code PATH] - | environment variable, as this can lead to unexpected results, especially - | when using #[code virtualenv]. Instead, commands need to be prefixed with - | #[code python -m]. - -+infobox("Solution") - | Run the command with #[code python -m], for example - | #[code python -m spacy download en]. For more info on this, see the - | #[+a("/docs/usage/cli") CLI documentation]. - -+h(3, "module-load") 'module' object has no attribute 'load' - -+code(false, "text"). - AttributeError: 'module' object has no attribute 'load' - -p - | While this could technically have many causes, including spaCy being - | broken, the most likely one is that your script's file or directory name - | is "shadowing" the module – e.g. your file is called #[code spacy.py], - | or a directory you're importing from is called #[code spacy]. - -+infobox("Solution") - | When using spaCy, never call anything else #[code spacy]. - -+h(2, "usage") Using spaCy - -+h(3, "pos-lemma-number") POS tag or lemma is returned as number - -+code. - doc = nlp(u'This is text.') - print([word.pos for word in doc]) - # [88, 98, 90, 95] - -p - | Like many NLP libraries, spaCy encodes all strings to integers. This - | reduces memory usage and improves efficiency. The integer mapping also - | makes it easy to interoperate with numpy. To access the string - | representation instead of the integer ID, add an underscore #[code _] - | after the attribute. - -+infobox("Solutions") - | Use #[code pos_] or #[code lemma_] instead. See the - | #[+api("token#attributes") #[code Token] attributes] for a list of available - | attributes and their string representations. - - -+h(3, "pron-lemma") Pronoun lemma is returned as #[code -PRON-] - -+code. - doc = nlp(u'They are') - print(doc[0].lemma_) - # -PRON- - -p - | This is in fact expected behaviour and not a bug. - | Unlike verbs and common nouns, there's no clear base form of a personal - | pronoun. Should the lemma of "me" be "I", or should we normalize person - | as well, giving "it" — or maybe "he"? spaCy's solution is to introduce a - | novel symbol, #[code -PRON-], which is used as the lemma for - | all personal pronouns. For more info on this, see the - | #[+api("annotation#lemmatization") annotation specs] on lemmatization. diff --git a/website/docs/usage/v2.jade b/website/docs/usage/v2.jade index ca026bd20..d9727c62b 100644 --- a/website/docs/usage/v2.jade +++ b/website/docs/usage/v2.jade @@ -2,9 +2,530 @@ include ../../_includes/_mixins +p + | We're very excited to finally introduce spaCy v2.0! On this page, you'll + | find a summary of the new features, information on the backwards + | incompatibilities, including a handy overview of what's been renamed or + | deprecated. To help you make the most of v2.0, we also + | #[strong re-wrote almost all of the usage guides and API docs], and added + | more real-world examples. If you're new to spaCy, or just want to brush + | up on some NLP basics and the details of the library, check out + | the #[+a("/docs/usage/spacy-101") spaCy 101 guide] that explains the most + | important concepts with examples and illustrations. + ++h(2, "summary") Summary + ++grid.o-no-block + +grid-col("half") + + p This release features + | entirely new #[strong deep learning-powered models] for spaCy's tagger, + | parser and entity recognizer. The new models are #[strong 20x smaller] + | than the linear models that have powered spaCy until now: from 300 MB to + | only 15 MB. + + p + | We've also made several usability improvements that are + | particularly helpful for #[strong production deployments]. spaCy + | v2 now fully supports the Pickle protocol, making it easy to use + | spaCy with #[+a("https://spark.apache.org/") Apache Spark]. The + | string-to-integer mapping is #[strong no longer stateful], making + | it easy to reconcile annotations made in different processes. + | Models are smaller and use less memory, and the APIs for serialization + | are now much more consistent. + + +table-of-contents + +item #[+a("#summary") Summary] + +item #[+a("#features") New features] + +item #[+a("#features-pipelines") Improved processing pipelines] + +item #[+a("#features-text-classification") Text classification] + +item #[+a("#features-hash-ids") Hash values instead of integer IDs] + +item #[+a("#features-serializer") Saving, loading and serialization] + +item #[+a("#features-displacy") displaCy visualizer] + +item #[+a("#features-language") Language data and lazy loading] + +item #[+a("#features-matcher") Revised matcher API] + +item #[+a("#features-models") Neural network models] + +item #[+a("#incompat") Backwards incompatibilities] + +item #[+a("#migrating") Migrating from spaCy v1.x] + +item #[+a("#benchmarks") Benchmarks] + +p + | The main usability improvements you'll notice in spaCy v2.0 are around + | #[strong defining, training and loading your own models] and components. + | The new neural network models make it much easier to train a model from + | scratch, or update an existing model with a few examples. In v1.x, the + | statistical models depended on the state of the #[code Vocab]. If you + | taught the model a new word, you would have to save and load a lot of + | data — otherwise the model wouldn't correctly recall the features of your + | new example. That's no longer the case. + +p + | Due to some clever use of hashing, the statistical models + | #[strong never change size], even as they learn new vocabulary items. + | The whole pipeline is also now fully differentiable. Even if you don't + | have explicitly annotated data, you can update spaCy using all the + | #[strong latest deep learning tricks] like adversarial training, noise + | contrastive estimation or reinforcement learning. +h(2, "features") New features +p + | This section contains an overview of the most important + | #[strong new features and improvements]. The #[+a("/docs/api") API docs] + | include additional deprecation notes. New methods and functions that + | were introduced in this version are marked with a #[+tag-new(2)] tag. + ++h(3, "features-pipelines") Improved processing pipelines + ++aside-code("Example"). + # Modify an existing pipeline + nlp = spacy.load('en') + nlp.pipeline.append(my_component) + + # Register a factory to create a component + spacy.set_factory('my_factory', my_factory) + nlp = Language(pipeline=['my_factory', mycomponent]) + +p + | It's now much easier to #[strong customise the pipeline] with your own + | components, functions that receive a #[code Doc] object, modify and + | return it. If your component is stateful, you can define and register a + | factory which receives the shared #[code Vocab] object and returns a + |  component. spaCy's default components can be added to your pipeline by + | using their string IDs. This way, you won't have to worry about finding + | and implementing them – simply add #[code "tagger"] to the pipeline, + | and spaCy will know what to do. + ++image + include ../../assets/img/docs/pipeline.svg + ++infobox + | #[strong API:] #[+api("language") #[code Language]] + | #[strong Usage:] #[+a("/docs/usage/language-processing-pipeline") Processing text] + ++h(3, "features-text-classification") Text classification + ++aside-code("Example"). + from spacy.lang.en import English + nlp = English(pipeline=['tensorizer', 'tagger', 'textcat']) + +p + | spaCy v2.0 lets you add text categorization models to spaCy pipelines. + | The model supports classification with multiple, non-mutually exclusive + | labels – so multiple labels can apply at once. You can change the model + | architecture rather easily, but by default, the #[code TextCategorizer] + | class uses a convolutional neural network to assign position-sensitive + | vectors to each word in the document. + ++infobox + | #[strong API:] #[+api("textcategorizer") #[code TextCategorizer]], + | #[+api("doc#attributes") #[code Doc.cats]], + | #[+api("goldparse#attributes") #[code GoldParse.cats]]#[br] + | #[strong Usage:] #[+a("/docs/usage/text-classification") Text classification] + ++h(3, "features-hash-ids") Hash values instead of integer IDs + ++aside-code("Example"). + doc = nlp(u'I love coffee') + assert doc.vocab.strings[u'coffee'] == 3197928453018144401 + assert doc.vocab.strings[3197928453018144401] == u'coffee' + + beer_hash = doc.vocab.strings.add(u'beer') + assert doc.vocab.strings[u'beer'] == beer_hash + assert doc.vocab.strings[beer_hash] == u'beer' + +p + | The #[+api("stringstore") #[code StringStore]] now resolves all strings + | to hash values instead of integer IDs. This means that the string-to-int + | mapping #[strong no longer depends on the vocabulary state], making a lot + | of workflows much simpler, especially during training. Unlike integer IDs + | in spaCy v1.x, hash values will #[strong always match] – even across + | models. Strings can now be added explicitly using the new + | #[+api("stringstore#add") #[code Stringstore.add]] method. A token's hash + | is available via #[code token.orth]. + ++infobox + | #[strong API:] #[+api("stringstore") #[code StringStore]] + | #[strong Usage:] #[+a("/docs/usage/spacy-101#vocab") Vocab, hashes and lexemes 101] + ++h(3, "features-serializer") Saving, loading and serialization + ++aside-code("Example"). + nlp = spacy.load('en') # shortcut link + nlp = spacy.load('en_core_web_sm') # package + nlp = spacy.load('/path/to/en') # unicode path + nlp = spacy.load(Path('/path/to/en')) # pathlib Path + + nlp.to_disk('/path/to/nlp') + nlp = English().from_disk('/path/to/nlp') + +p + | spay's serialization API has been made consistent across classes and + | objects. All container classes, i.e. #[code Language], #[code Doc], + | #[code Vocab] and #[code StringStore] now have a #[code to_bytes()], + | #[code from_bytes()], #[code to_disk()] and #[code from_disk()] method + | that supports the Pickle protocol. + +p + | The improved #[code spacy.load] makes loading models easier and more + | transparent. You can load a model by supplying its + | #[+a("/docs/usage/models#usage") shortcut link], the name of an installed + | #[+a("/docs/usage/saving-loading#generating") model package] or a path. + | The #[code Language] class to initialise will be determined based on the + | model's settings. For a blank language, you can import the class directly, + | e.g. #[code from spacy.lang.en import English]. + ++infobox + | #[strong API:] #[+api("spacy#load") #[code spacy.load]], #[+api("binder") #[code Binder]] + | #[strong Usage:] #[+a("/docs/usage/saving-loading") Saving and loading] + ++h(3, "features-displacy") displaCy visualizer with Jupyter support + ++aside-code("Example"). + from spacy import displacy + doc = nlp(u'This is a sentence about Facebook.') + displacy.serve(doc, style='dep') # run the web server + html = displacy.render(doc, style='ent') # generate HTML + +p + | Our popular dependency and named entity visualizers are now an official + | part of the spaCy library! displaCy can run a simple web server, or + | generate raw HTML markup or SVG files to be exported. You can pass in one + | or more docs, and customise the style. displaCy also auto-detects whether + | you're running #[+a("https://jupyter.org") Jupyter] and will render the + | visualizations in your notebook. + ++infobox + | #[strong API:] #[+api("displacy") #[code displacy]] + | #[strong Usage:] #[+a("/docs/usage/visualizers") Visualizing spaCy] + ++h(3, "features-language") Improved language data and lazy loading + +p + | Language-specfic data now lives in its own submodule, #[code spacy.lang]. + | Languages are lazy-loaded, i.e. only loaded when you import a + | #[code Language] class, or load a model that initialises one. This allows + | languages to contain more custom data, e.g. lemmatizer lookup tables, or + | complex regular expressions. The language data has also been tidied up + | and simplified. spaCy now also supports simple lookup-based lemmatization. + ++infobox + | #[strong API:] #[+api("language") #[code Language]] + | #[strong Code:] #[+src(gh("spaCy", "spacy/lang")) spacy/lang] + | #[strong Usage:] #[+a("/docs/usage/adding-languages") Adding languages] + ++h(3, "features-matcher") Revised matcher API + ++aside-code("Example"). + from spacy.matcher import Matcher + matcher = Matcher(nlp.vocab) + matcher.add('HEARTS', None, [{'ORTH': '❤️', 'OP': '+'}]) + assert len(matcher) == 1 + assert 'HEARTS' in matcher + +p + | Patterns can now be added to the matcher by calling + | #[+api("matcher-add") #[code matcher.add()]] with a match ID, an optional + | callback function to be invoked on each match, and one or more patterns. + | This allows you to write powerful, pattern-specific logic using only one + | matcher. For example, you might only want to merge some entity types, + | and set custom flags for other matched patterns. + ++infobox + | #[strong API:] #[+api("matcher") #[code Matcher]] + | #[strong Usage:] #[+a("/docs/usage/rule-based-matching") Rule-based matching] + ++h(3, "features-models") Neural network models for English, German, French, Spanish and multi-language NER + ++aside-code("Example", "bash"). + python -m spacy download en # default English model + python -m spacy download de # default German model + python -m spacy download fr # default French model + python -m spacy download es # default Spanish model + python -m spacy download xx_ent_wiki_sm # multi-language NER + +p + | spaCy v2.0 comes with new and improved neural network models for English, + | German, French and Spanish, as well as a multi-language named entity + | recognition model trained on Wikipedia. #[strong GPU usage] is now + | supported via #[+a("http://chainer.org") Chainer]'s CuPy module. + ++infobox + | #[strong Details:] #[+a("/docs/api/language-models") Languages], + | #[+src(gh("spacy-models")) spacy-models] + | #[strong Usage:] #[+a("/docs/usage/models") Models], + | #[+a("/docs/usage#gpu") Using spaCy with GPU] + +h(2, "incompat") Backwards incompatibilities ++table(["Old", "New"]) + +row + +cell + | #[code spacy.en] + | #[code spacy.xx] + +cell + | #[code spacy.lang.en] + | #[code spacy.lang.xx] + + +row + +cell #[code orth] + +cell #[code lang.xx.lex_attrs] + + +row + +cell #[code syntax.iterators] + +cell #[code lang.xx.syntax_iterators] + + +row + +cell #[code Language.save_to_directory] + +cell #[+api("language#to_disk") #[code Language.to_disk]] + + +row + +cell #[code Language.create_make_doc] + +cell #[+api("language#attributes") #[code Language.tokenizer]] + + +row + +cell + | #[code Vocab.load] + | #[code Vocab.load_lexemes] + +cell + | #[+api("vocab#from_disk") #[code Vocab.from_disk]] + | #[+api("vocab#from_bytes") #[code Vocab.from_bytes]] + + +row + +cell + | #[code Vocab.dump] + +cell + | #[+api("vocab#to_disk") #[code Vocab.to_disk]]#[br] + | #[+api("vocab#to_bytes") #[code Vocab.to_bytes]] + + +row + +cell + | #[code Vocab.load_vectors] + | #[code Vocab.load_vectors_from_bin_loc] + +cell + | #[+api("vectors#from_disk") #[code Vectors.from_disk]] + | #[+api("vectors#from_bytes") #[code Vectors.from_bytes]] + + +row + +cell + | #[code Vocab.dump_vectors] + +cell + | #[+api("vectors#to_disk") #[code Vectors.to_disk]] + | #[+api("vectors#to_bytes") #[code Vectors.to_bytes]] + + +row + +cell + | #[code StringStore.load] + +cell + | #[+api("stringstore#from_disk") #[code StringStore.from_disk]] + | #[+api("stringstore#from_bytes") #[code StringStore.from_bytes]] + + +row + +cell + | #[code StringStore.dump] + +cell + | #[+api("stringstore#to_disk") #[code StringStore.to_disk]] + | #[+api("stringstore#to_bytes") #[code StringStore.to_bytes]] + + +row + +cell #[code Tokenizer.load] + +cell + | #[+api("tokenizer#from_disk") #[code Tokenizer.from_disk]] + | #[+api("tokenizer#from_bytes") #[code Tokenizer.from_bytes]] + + +row + +cell #[code Tagger.load] + +cell + | #[+api("tagger#from_disk") #[code Tagger.from_disk]] + | #[+api("tagger#from_bytes") #[code Tagger.from_bytes]] + + +row + +cell #[code DependencyParser.load] + +cell + | #[+api("dependencyparser#from_disk") #[code DependencyParser.from_disk]] + | #[+api("dependencyparser#from_bytes") #[code DependencyParser.from_bytes]] + + +row + +cell #[code EntityRecognizer.load] + +cell + | #[+api("entityrecognizer#from_disk") #[code EntityRecognizer.from_disk]] + | #[+api("entityrecognizer#from_bytes") #[code EntityRecognizer.from_bytes]] + + +row + +cell #[code Matcher.load] + +cell - + + +row + +cell + | #[code Matcher.add_pattern] + | #[code Matcher.add_entity] + +cell #[+api("matcher#add") #[code Matcher.add]] + + +row + +cell #[code Matcher.get_entity] + +cell #[+api("matcher#get") #[code Matcher.get]] + + +row + +cell #[code Matcher.has_entity] + +cell #[+api("matcher#contains") #[code Matcher.__contains__]] + + +row + +cell #[code Doc.read_bytes] + +cell #[+api("binder") #[code Binder]] + + +row + +cell #[code Token.is_ancestor_of] + +cell #[+api("token#is_ancestor") #[code Token.is_ancestor]] + + +row + +cell #[code cli.model] + +cell - + +h(2, "migrating") Migrating from spaCy 1.x + +p + | Because we'e made so many architectural changes to the library, we've + | tried to #[strong keep breaking changes to a minimum]. A lot of projects + | follow the philosophy that if you're going to break anything, you may as + | well break everything. We think migration is easier if there's a logic to + | what has changed. + +p + | We've therefore followed a policy of avoiding breaking changes to the + | #[code Doc], #[code Span] and #[code Token] objects. This way, you can + | focus on only migrating the code that does training, loading and + | serialization — in other words, code that works with the #[code nlp] + | object directly. Code that uses the annotations should continue to work. + ++infobox("Important note") + | If you've trained your own models, keep in mind that your train and + | runtime inputs must match. This means you'll have to + | #[strong retrain your models] with spaCy v2.0. + ++h(3, "migrating-saving-loading") Saving, loading and serialization + +p + | Double-check all calls to #[code spacy.load()] and make sure they don't + | use the #[code path] keyword argument. If you're only loading in binary + | data and not a model package that can construct its own #[code Language] + | class and pipeline, you should now use the + | #[+api("language#from_disk") #[code Language.from_disk()]] method. + ++code-new. + nlp = spacy.load('/model') + nlp = English().from_disk('/model/data') ++code-old nlp = spacy.load('en', path='/model') + +p + | Review all other code that writes state to disk or bytes. + | All containers, now share the same, consistent API for saving and + | loading. Replace saving with #[code to_disk()] or #[code to_bytes()], and + | loading with #[code from_disk()] and #[code from_bytes()]. + ++code-new. + nlp.to_disk('/model') + nlp.vocab.to_disk('/vocab') + ++code-old. + nlp.save_to_directory('/model') + nlp.vocab.dump('/vocab') + +p + | If you've trained models with input from v1.x, you'll need to + | #[strong retrain them] with spaCy v2.0. All previous models will not + | be compatible with the new version. + ++h(3, "migrating-strings") Strings and hash values + +p + | The change from integer IDs to hash values may not actually affect your + | code very much. However, if you're adding strings to the vocab manually, + | you now need to call #[+api("stringstore#add") #[code StringStore.add()]] + | explicitly. You can also now be sure that the string-to-hash mapping will + | always match across vocabularies. + ++code-new. + nlp.vocab.strings.add(u'coffee') + nlp.vocab.strings[u'coffee'] # 3197928453018144401 + other_nlp.vocab.strings[u'coffee'] # 3197928453018144401 + ++code-old. + nlp.vocab.strings[u'coffee'] # 3672 + other_nlp.vocab.strings[u'coffee'] # 40259 + ++h(3, "migrating-languages") Processing pipelines and language data + +p + | If you're importing language data or #[code Language] classes, make sure + | to change your import statements to import from #[code spacy.lang]. If + | you've added your own custom language, it needs to be moved to + | #[code spacy/lang/xx] and adjusted accordingly. + ++code-new from spacy.lang.en import English ++code-old from spacy.en import English + +p + | If you've been using custom pipeline components, check out the new + | guide on #[+a("/docs/usage/language-processing-pipelines") processing pipelines]. + | Appending functions to the pipeline still works – but you might be able + | to make this more convenient by registering "component factories". + | Components of the processing pipeline can now be disabled by passing a + | list of their names to the #[code disable] keyword argument on loading + | or processing. + ++code-new. + nlp = spacy.load('en', disable=['tagger', 'ner']) + doc = nlp(u"I don't want parsed", disable=['parser']) ++code-old. + nlp = spacy.load('en', tagger=False, entity=False) + doc = nlp(u"I don't want parsed", parse=False) + ++h(3, "migrating-matcher") Adding patterns and callbacks to the matcher + +p + | If you're using the matcher, you can now add patterns in one step. This + | should be easy to update – simply merge the ID, callback and patterns + | into one call to #[+api("matcher#add") #[code Matcher.add()]]. + ++code-new. + matcher.add('GoogleNow', merge_phrases, [{ORTH: 'Google'}, {ORTH: 'Now'}]) + ++code-old. + matcher.add_entity('GoogleNow', on_match=merge_phrases) + matcher.add_pattern('GoogleNow', [{ORTH: 'Google'}, {ORTH: 'Now'}]) + +p + | If you've been using #[strong acceptor functions], you'll need to move + | this logic into the + | #[+a("/docs/usage/rule-based-matching#on_match") #[code on_match] callbacks]. + | The callback function is invoked on every match and will give you access to + | the doc, the index of the current match and all total matches. This lets + | you both accept or reject the match, and define the actions to be + | triggered. + ++h(2, "benchmarks") Benchmarks + ++under-construction + ++aside("Data sources") + | #[strong Parser, tagger, NER:] #[+a("https://www.gabormelli.com/RKB/OntoNotes_Corpus") OntoNotes 5]#[br] + | #[strong Word vectors:] #[+a("http://commoncrawl.org") Common Crawl]#[br] + +p The evaluation was conducted on raw text with no gold standard information. + ++table(["Model", "Version", "Type", "UAS", "LAS", "NER F", "POS", "w/s"]) + mixin benchmark-row(name, details, values, highlight, style) + +row(style) + +cell #[code=name] + for cell in details + +cell=cell + for cell, i in values + +cell.u-text-right + if highlight && highlight[i] + strong=cell + else + !=cell + + +benchmark-row("en_core_web_sm", ["2.0.0", "neural"], ["91.2", "89.2", "82.6", "96.6", "10,300"], [1, 1, 1, 0, 0]) + +benchmark-row("en_core_web_sm", ["1.2.0", "linear"], ["86.6", "83.8", "78.5", "96.6", "25,700"], [0, 0, 0, 0, 1], "divider") + +benchmark-row("en_core_web_md", ["1.2.1", "linear"], ["90.6", "88.5", "81.4", "96.7", "18,800"], [0, 0, 0, 1, 0]) diff --git a/website/docs/usage/visualizers.jade b/website/docs/usage/visualizers.jade index ea675e70c..b3cbd3b46 100644 --- a/website/docs/usage/visualizers.jade +++ b/website/docs/usage/visualizers.jade @@ -4,7 +4,7 @@ include ../../_includes/_mixins p | As of v2.0, our popular visualizers, #[+a(DEMOS_URL + "/displacy") displaCy] - | and #[+a(DEMOS_URL + "displacy-ent") displaCy #[sup ENT]] are finally an + | and #[+a(DEMOS_URL + "/displacy-ent") displaCy #[sup ENT]] are finally an | official part of the library. Visualizing a dependency parse or named | entities in a text is not only a fun NLP demo – it can also be incredibly | helpful in speeding up development and debugging your code and training @@ -29,6 +29,7 @@ p | standards. +h(2, "getting-started") Getting started + +tag-new(2) p | The quickest way visualize #[code Doc] is to use @@ -58,6 +59,13 @@ p | The argument #[code options] lets you specify a dictionary of settings | to customise the layout, for example: ++aside("Important note") + | There's currently a known issue with the #[code compact] mode for + | sentences with short arrows and long dependency labels, that causes labels + | longer than the arrow to wrap. So if you come across this problem, + | especially when using custom labels, you'll have to increase the + | #[code distance] setting in the #[code options] to allow longer arcs. + +table(["Name", "Type", "Description", "Default"]) +row +cell #[code compact] @@ -180,8 +188,8 @@ p p | If you don't need the web server and just want to generate the markup | – for example, to export it to a file or serve it in a custom - | way – you can use #[+api("displacy#render") #[code displacy.render]] - | instead. It works the same, but returns a string containing the markup. + | way – you can use #[+api("displacy#render") #[code displacy.render]]. + | It works the same way, but returns a string containing the markup. +code("Example"). import spacy @@ -220,10 +228,32 @@ p | a standalone graphic.) So instead of rendering all #[code Doc]s at one, | loop over them and export them separately. + ++h(3, "examples-export-svg") Example: Export SVG graphics of dependency parses + ++code("Example"). + import spacy + from spacy import displacy + from pathlib import Path + + nlp = spacy.load('en') + sentences = ["This is an example.", "This is another one."] + for sent in sentences: + doc = nlp(sentence) + svg = displacy.render(doc, style='dep') + file_name = '-'.join([w.text for w in doc if not w.is_punct]) + '.svg' + output_path = Path('/images/' + file_name) + output_path.open('w', encoding='utf-8').write(svg) + +p + | The above code will generate the dependency visualizations and them to + | two files, #[code This-is-an-example.svg] and #[code This-is-another-one.svg]. + + +h(2, "jupyter") Using displaCy in Jupyter notebooks p - | displaCy is able to detect whether you're within a + | displaCy is able to detect whether you're working in a | #[+a("https://jupyter.org") Jupyter] notebook, and will return markup | that can be rendered in a cell straight away. When you export your | notebook, the visualizations will be included as HTML. @@ -257,28 +287,6 @@ p html = displacy.render(doc, style='dep') return display(HTML(html)) -+h(2, "examples") Usage examples - -+h(3, "examples-export-svg") Export SVG graphics of dependency parses - -+code("Example"). - import spacy - from spacy import displacy - from pathlib import Path - - nlp = spacy.load('en') - sentences = ["This is an example.", "This is another one."] - for sent in sentences: - doc = nlp(sentence) - svg = displacy.render(doc, style='dep') - file_name = '-'.join([w.text for w in doc if not w.is_punct]) + '.svg' - output_path = Path('/images/' + file_name) - output_path.open('w', encoding='utf-8').write(svg) - -p - | The above code will generate the dependency visualizations and them to - | two files, #[code This-is-an-example.svg] and #[code This-is-another-one.svg]. - +h(2, "manual-usage") Rendering data manually p @@ -287,24 +295,17 @@ p | #[+a("http://www.nltk.org") NLTK] or | #[+a("https://github.com/tensorflow/models/tree/master/syntaxnet") SyntaxNet]. | Simply convert the dependency parse or recognised entities to displaCy's - | format and import #[code DependencyRenderer] or #[code EntityRenderer] - | from #[code spacy.displacy.render]. A renderer class can be is initialised - | with a dictionary of options. To generate the visualization markup, call - | the renderer's #[code render()] method on a list of dictionaries (one - | per visualization). - + | format and set #[code manual=True] on either #[code render()] or + | #[code serve()]. +aside-code("Example"). - from spacy.displacy.render import EntityRenderer - ex = [{'text': 'But Google is starting from behind.', 'ents': [{'start': 4, 'end': 10, 'label': 'ORG'}], 'title': None}] - renderer = EntityRenderer() - html = renderer.render(ex) + html = displacy.render(ex, style='ent', manual=True) -+code("DependencyRenderer input"). - [{ ++code("DEP input"). + { 'words': [ {'text': 'This', 'tag': 'DT'}, {'text': 'is', 'tag': 'VBZ'}, @@ -314,11 +315,70 @@ p {'start': 0, 'end': 1, 'label': 'nsubj', 'dir': 'left'}, {'start': 2, 'end': 3, 'label': 'det', 'dir': 'left'}, {'start': 1, 'end': 3, 'label': 'attr', 'dir': 'right'}] - }] + } -+code("EntityRenderer input"). - [{ ++code("ENT input"). + { 'text': 'But Google is starting from behind.', 'ents': [{'start': 4, 'end': 10, 'label': 'ORG'}], 'title': None - }] + } + ++h(2, "webapp") Using displaCy in a web application + +p + | If you want to use the visualizers as part of a web application, for + | example to create something like our + | #[+a(DEMOS_URL + "/displacy") online demo], it's not recommended to + | simply wrap and serve the displaCy renderer. Instead, you should only + | rely on the server to perform spaCy's processing capabilities, and use + | #[+a(gh("displacy")) displaCy.js] to render the JSON-formatted output. + ++aside("Why not return the HTML by the server?") + | It's certainly possible to just have your server return the markup. + | But outputting raw, unsanitised HTML is risky and makes your app vulnerable to + | #[+a("https://en.wikipedia.org/wiki/Cross-site_scripting") cross-site scripting] + | (XSS). All your user needs to do is find a way to make spaCy return text + | like #[code <script src="malicious-code.js"><script>], which + | is pretty easy in NER mode. Instead of relying on the server to render + | and sanitise HTML, you can do this on the client in JavaScript. + | displaCy.js creates the markup as DOM nodes and will never insert raw + | HTML. + +p + | The #[code parse_deps] function takes a #[code Doc] object and returns + | a dictionary in a format that can be rendered by displaCy. + ++code("Example"). + import spacy + from spacy import displacy + + nlp = spacy.load('en') + + def displacy_service(text): + doc = nlp(text) + return displacy.parse_deps(doc) + +p + | Using a library like #[+a("https://falconframework.org/") Falcon] or + | #[+a("http://www.hug.rest/") Hug], you can easily turn the above code + | into a simple REST API that receives a text and returns a JSON-formatted + | parse. In your front-end, include #[+a(gh("displacy")) displacy.js] and + | initialise it with the API URL and the ID or query selector of the + | container to render the visualisation in, e.g. #[code '#displacy'] for + | #[code <div id="displacy">]. + ++code("script.js", "javascript"). + var displacy = new displaCy('http://localhost:8080', { + container: '#displacy' + }) + + function parse(text) { + displacy.parse(text); + } + +p + | When you call #[code parse()], it will make a request to your API, + | receive the JSON-formatted parse and render it in your container. To + | create an interactive experience, you could trigger this function by + | a button and read the text from an #[code <input>] field. diff --git a/website/docs/usage/word-vectors-similarities.jade b/website/docs/usage/word-vectors-similarities.jade index 3cc0a67a8..937fbfbd0 100644 --- a/website/docs/usage/word-vectors-similarities.jade +++ b/website/docs/usage/word-vectors-similarities.jade @@ -6,61 +6,154 @@ p | Dense, real valued vectors representing distributional similarity | information are now a cornerstone of practical NLP. The most common way | to train these vectors is the #[+a("https://en.wikipedia.org/wiki/Word2vec") word2vec] - | family of algorithms. + | family of algorithms. The default + | #[+a("/docs/usage/models#available") English model] installs + | 300-dimensional vectors trained on the + | #[+a("http://commoncrawl.org") Common Crawl] corpus. -+aside("Tip") ++aside("Tip: Training a word2vec model") | If you need to train a word2vec model, we recommend the implementation in | the Python library #[+a("https://radimrehurek.com/gensim/") Gensim]. ++h(2, "101") Similarity and word vectors 101 + +tag-model("vectors") + +include _spacy-101/_similarity +include _spacy-101/_word-vectors + ++h(2, "similarity-context") Similarities in context + p - | spaCy makes using word vectors very easy. The - | #[+api("lexeme") #[code Lexeme]], #[+api("token") #[code Token]], - | #[+api("span") #[code Span]] and #[+api("doc") #[code Doc]] classes all - | have a #[code .vector] property, which is a 1-dimensional numpy array of - | 32-bit floats: + | Aside from spaCy's built-in word vectors, which were trained on a lot of + | text with a wide vocabulary, the parsing, tagging and NER models also + | rely on vector representations of the #[strong meanings of words in context]. + | As the first component of the + | #[+a("/docs/usage/language-processing-pipeline") processing pipeline], the + | tensorizer encodes a document's internal meaning representations as an + | array of floats, also called a tensor. This allows spaCy to make a + | reasonable guess at a word's meaning, based on its surrounding words. + | Even if a word hasn't been seen before, spaCy will know #[em something] + | about it. Because spaCy uses a 4-layer convolutional network, the + | tensors are sensitive to up to #[strong four words on either side] of a + | word. + +p + | For example, here are three sentences containing the out-of-vocabulary + | word "labrador" in different contexts. +code. - import numpy + doc1 = nlp(u"The labrador barked.") + doc2 = nlp(u"The labrador swam.") + doc3 = nlp(u"the labrador people live in canada.") - apples, and_, oranges = nlp(u'apples and oranges') - print(apples.vector.shape) - # (1,) - apples.similarity(oranges) + for doc in [doc1, doc2, doc3]: + labrador = doc[1] + dog = nlp(u"dog") + print(labrador.similarity(dog)) p - | By default, #[code Token.vector] returns the vector for its underlying - | lexeme, while #[code Doc.vector] and #[code Span.vector] return an - | average of the vectors of their tokens. You can customize these + | Even though the model has never seen the word "labrador", it can make a + | fairly accurate prediction of its similarity to "dog" in different + | contexts. + ++table(["Context", "labrador.similarity(dog)"]) + +row + +cell The #[strong labrador] barked. + +cell #[code 0.56] #[+procon("pro")] + + +row + +cell The #[strong labrador] swam. + +cell #[code 0.48] #[+procon("con")] + + +row + +cell the #[strong labrador] people live in canada. + +cell #[code 0.39] #[+procon("con")] + +p + | The same also works for whole documents. Here, the variance of the + | similarities is lower, as all words and their order are taken into + | account. However, the context-specific similarity is often still + | reflected pretty accurately. + ++code. + doc1 = nlp(u"Paris is the largest city in France.") + doc2 = nlp(u"Vilnius is the capital of Lithuania.") + doc3 = nlp(u"An emu is a large bird.") + + for doc in [doc1, doc2, doc3]: + for other_doc in [doc1, doc2, doc3]: + print(doc.similarity(other_doc)) + +p + | Even though the sentences about Paris and Vilnius consist of different + | words and entities, they both describe the same concept and are seen as + | more similar than the sentence about emus. In this case, even a misspelled + | version of "Vilnius" would still produce very similar results. + ++table + - var examples = {"Paris is the largest city in France.": [1, 0.85, 0.65], "Vilnius is the capital of Lithuania.": [0.85, 1, 0.55], "An emu is a large bird.": [0.65, 0.55, 1]} + - var counter = 0 + + +row + +row + +cell + for _, label in examples + +cell=label + + each cells, label in examples + +row(counter ? null : "divider") + +cell=label + for cell in cells + +cell.u-text-center #[code=cell.toFixed(2)] + | #[+procon(cell < 0.7 ? "con" : cell != 1 ? "pro" : "neutral")] + - counter++ + +p + | Sentences that consist of the same words in different order will likely + | be seen as very similar – but never identical. + ++code. + docs = [nlp(u"dog bites man"), nlp(u"man bites dog"), + nlp(u"man dog bites"), nlp(u"dog man bites")] + + for doc in docs: + for other_doc in docs: + print(doc.similarity(other_doc)) + +p + | Interestingly, "man bites dog" and "man dog bites" are seen as slightly + | more similar than "man bites dog" and "dog bites man". This may be a + | conincidence – or the result of "man" being interpreted as both sentence's + | subject. + ++table + - var examples = {"dog bites man": [1, 0.9, 0.89, 0.92], "man bites dog": [0.9, 1, 0.93, 0.9], "man dog bites": [0.89, 0.93, 1, 0.92], "dog man bites": [0.92, 0.9, 0.92, 1]} + - var counter = 0 + + +row + +row + +cell + for _, label in examples + +cell.u-text-center=label + + each cells, label in examples + +row(counter ? null : "divider") + +cell=label + for cell in cells + +cell.u-text-center #[code=cell.toFixed(2)] + | #[+procon(cell < 0.7 ? "con" : cell != 1 ? "pro" : "neutral")] + - counter++ + ++h(2, "custom") Customising word vectors + ++under-construction + +p + | By default, #[+api("token#vector") #[code Token.vector]] returns the + | vector for its underlying #[+api("lexeme") #[code Lexeme]], while + | #[+api("doc#vector") #[code Doc.vector]] and + | #[+api("span#vector") #[code Span.vector]] return an average of the + | vectors of their tokens. You can customize these | behaviours by modifying the #[code doc.user_hooks], | #[code doc.user_span_hooks] and #[code doc.user_token_hooks] | dictionaries. - -+aside-code("Example"). - # TODO - -p - | The default English model installs vectors for one million vocabulary - | entries, using the 300-dimensional vectors trained on the Common Crawl - | corpus using the #[+a("http://nlp.stanford.edu/projects/glove/") GloVe] - | algorithm. The GloVe common crawl vectors have become a de facto - | standard for practical NLP. - -+aside-code("Example"). - # TODO - -p - | You can load new word vectors from a file-like buffer using the - | #[code vocab.load_vectors()] method. The file should be a - | whitespace-delimited text file, where the word is in the first column, - | and subsequent columns provide the vector data. For faster loading, you - | can use the #[code vocab.vectors_from_bin_loc()] method, which accepts a - | path to a binary file written by #[code vocab.dump_vectors()]. - -+aside-code("Example"). - # TODO - -p - | You can also load vectors from memory, by writing to the #[code lexeme.vector] - | property. If the vectors you are writing are of different dimensionality - | from the ones currently loaded, you should first call - | #[code vocab.resize_vectors(new_size)]. diff --git a/website/index.jade b/website/index.jade index 17b564b42..741db53cf 100644 --- a/website/index.jade +++ b/website/index.jade @@ -11,7 +11,7 @@ include _includes/_mixins h2.c-landing__title.o-block.u-heading-1 | in Python - +landing-badge("https://survey.spacy.io", "usersurvey", "Take the user survey!") + +landing-badge(gh("spaCy") + "/releases/tag/v2.0.0-alpha", "v2alpha", "Try spaCy v2.0.0 alpha!") +grid.o-content +grid-col("third").o-card @@ -97,7 +97,7 @@ include _includes/_mixins +item Part-of-speech tagging +item #[strong Named entity] recognition +item Labelled dependency parsing - +item Convenient string-to-int mapping + +item Convenient string-to-hash mapping +item Export to numpy data arrays +item GIL-free #[strong multi-threading] +item Efficient binary serialization diff --git a/website/robots.txt.jade b/website/robots.txt.jade index 8159ab664..437b98c30 100644 --- a/website/robots.txt.jade +++ b/website/robots.txt.jade @@ -3,3 +3,7 @@ if environment != "deploy" | User-agent: * | Disallow: / + +if ALPHA + | User-agent: Googlebot + | Disallow: /