Quimify Classifier is the model that sorts a chemistry query into its category before it reaches the resolvers. Given an input string (a formula or a name), it decides which of these 4 classes it belongs to. It is used in Quimify.
inorganicFormula, e.g.H2SO4,NaClorganicFormula, e.g.CH3-CH2-OHinorganicName, e.g.cloruro de sodioorganicName, e.g.2-bromo-2-cloropropano
It is not a single multiclass model but a tree of 3 binary classifiers chained with a
0.5 threshold:
input ──► formula-name ──┬─(formula)─► formula-inorganic-organic ──► inorganic / organic
└─(name)────► name-inorganic-organic ─────► inorganic / organic
Each of the 3 classifiers is a character-level Keras Sequential network. The
preprocessing step (Model.data_standardization) is the key piece: it lowercases the
input and inserts a space after every character via regex, so TextVectorization
tokenizes per character instead of per word:
"CH3-CH=CH" ──► "c h 3 - c h = c h"
Full pipeline:
TextVectorization(max_tokens=2048, output_sequence_length=16, standardize=data_standardization)
→ Embedding(max_features+1, embedding_dim=1024)
→ Dropout(0.2)
→ Dense(256, relu)
→ GlobalAveragePooling1D()
→ Dropout(0.2)
→ Dense(1) # logit
- Compilation:
adamoptimizer,BinaryCrossentropy(from_logits=True)loss,BinaryAccuracy(threshold=0.0)metric. data_standardizationis registered with@tf.keras.utils.register_keras_serializable()so it ships inside the SavedModel.- On export, the model is re-wrapped as
[TextVectorization, model, Activation("sigmoid")], so the artifact takes raw strings and returns a probability in[0, 1].
Clone the repository and install the dependencies (Python, tensorflow==2.12.0,
Flask==2.3.1):
pip install -r requirements.txtdata/split.sh reads data/source/ and builds data/split/ with a 90/10 train/test
hold-out, one file per line (the format text_dataset_from_directory expects):
cd data
./split.shIt refuses to run if data/split/ already holds data, so delete it to regenerate.
src/builder.ipynb is a parameterizable notebook. Pick which model to train through
the indices at the top:
model_prefix = ["", "formula-", "name-"][2] # -> "name-"
first_category = ["formula", "inorganic"][1] # -> "inorganic"
second_category = ["name", "organic"][1] # -> "organic"
# model_name = "name-inorganic-organic"It loads data/split/<model_name> with text_dataset_from_directory
(validation_split=0.2, seed=32), adapts TextVectorization on the train set, fits
for 5 epochs, plots accuracy and loss (matplotlib), evaluates on test, and exports to
models/<model_name>-model in SavedModel (tf) format. Run it three times, changing
the indices, to regenerate all 3 models.
src/api.py loads the 3 models from the given directory and starts the service. It
clears the Keras session after every request to avoid a memory leak:
cd src
python api.py ../modelstarget.sh copies api.py, model.py, requirements.txt and models/ into
target/<date>/:
./target.shdata/
source/ # raw corpus (one entry per line)
formulas.txt, names.txt
{inorganic,organic}-{formulas,names}.txt
random/ # generated subsets (16k, 260k organic)
split.sh # builds the train/test tree from source/
models/ # 3 exported models in SavedModel (.pb) format
formula-name-model/
formula-inorganic-organic-model/
name-inorganic-organic-model/
src/
model.py # Model class: architecture, standardization, load/predict
builder.ipynb # training notebook (data → fit → export)
api.py # inference service (Flask)
target.sh # packages a dated build for deployment
requirements.txt # Flask==2.3.1, tensorflow==2.12.0