Changelog¶
All notable changes to this project are documented here. Format follows Keep a Changelog.
[2.0.0] — 2026-10-19¶
Added¶
- Console output redesigned: one summary line per stage, a progress bar while training and on the slow graph-building steps, and a per-class table at the end;
verbose=0/1/2onTextPipeline, the builders andGNN TrainingResult.best_epoch,metrics,history,reportandconfusion_matrix(pandas, with class names);GraphBuildStats.word_word_edgesandsummary()text4gcn.set_verbosity()andTEXT4GCN_VERBOSITYfor diagnostic logs, through the standardloggingmodule (NullHandlerby default)- Tutorials: five notebooks in
docs/tutorials/(text as a graph → first classifier on 20 Newsgroups → comparing graph builders → refining the graph → your own corpus), rendered in the docs withmkdocs-jupyter text4gcn.datasets.save_corpus()writes any labelled corpus in the.txt/.metainput format, with validationtext4gcn.datasets.twenty_newsgroups()downloads 20 Newsgroups (scikit-learn) in the input formatdataset_pathacceptspathlib.Pathas well asstr(TextPipeline, builders,GNN, datasets)Builder.Bertselects theSemantic.bert()graph inGNNGraphBuildStatsreturned by every builder method (nodes, nnz, density, file size)TextPipeline(seed=…)for reproducible corpus shuffle- Optional
text4gcn[syntactic]extra (Stanford CoreNLP Python wrapper) andtext4gcn[bert]extra (transformers, for the BERT builder) scripts/reproduce_dcai2022.pyfor DCAI 2022 Tables 2/3 reference + local comparison- MkDocs + Material theme documentation; CI builds with
mkdocs build --strict - Graph refinement (
RefinementConfig,apply_refinement,NodeMetadata) GNN.fit()returnsTrainingResult; early-stop restores best validation weights
Changed¶
TrainingResultis a frozen dataclass andmodel_pathis always set;Semantic.cosine_similarity()and its builder class share the 0.95 defaultTextPipelinedownloads the NLTK resources only when they are missing, quietly- Requires PyTorch ≥ 2.3 (earlier builds cannot run with NumPy 2)
transformersis no longer installed by default: it is only needed bySemantic.bert(), via thebertextra- Training seed is deterministic (
GNN(seed=…)seeds torch / NumPy / Python); no random offset - Dropout from
GNN(dropout=…)is passed into the GCN;model.train()after each eval epoch Semantic.word2vec(threshold=…)defaults to 0.7 instead of 0.95: with the 20 epochs now actually applied, 0.95 left almost no word–word edges (79 on 20 Newsgroups, 4 categories)- RWF word–word weights are the pair count divided by the largest pair count, in (0, 1]; PMI is a gate only. (A Max–Min scaling gave the least frequent pairs weight 0, which deleted their edges.)
Layerisstr, Enumwith onlyLayer.GCN(GAT removed from the public API)- CoreNLP import is lazy; syntactic builders require the optional extra
- Documentation CI uses MkDocs (replacing Sphinx)
- Development tooling: Ruff replaces black / isort / flake8; one CI workflow lints, tests on Python 3.10–3.13 and builds the package; publishing to PyPI checks that the release tag matches the package version and runs the tests first
- Corpus is read as UTF-8 by default (was Latin-1); all intermediate files are written/read as UTF-8 regardless of OS locale
datasets.download(name, path)replacesdata.R8()/R52()/AG_NEWS(): R8, R52, Ohsumed and PubMed from thedatasets-v1GitHub release, with SHA-256 verification (the old Azure storage is gone)
Migrating from 1.x¶
- Requires Python ≥ 3.10 (1.x targeted Python 3.8)
- No
argparseis involved anymore:GNNworks in notebooks/Colab (fixes #3) GNN(dataset=…, path=…, epoches=…)→GNN(dataset_name=…, dataset_path=…, epochs=…)builderis optional (defaultBuilder.Tfidf) (fixes #2);Builder.Embedding/Builder.Frequency→Builder.Word2Vec/Builder.Tfidf- Import from the package root:
from text4gcn import GNN, TextPipeline, Builder, Layer Buildervalues changed ('frequency'→'frequency.tfidf','embedding'→'embedding.word2vec','cosine'→'cosine.similarity','dependency'→'dependency.parsing'), and so did the adjacency file names: graphs built with 1.x are not found; build them again- Graph files are no longer interchangeable with 1.x even for the same builder: Word2Vec, LIWC, cosine-similarity and RWF weights changed (see Fixed)
Removed¶
- The custom
modules.logger(PrintLog,Process),graph.report_stats()andmodels.show_metrics(): progress goes throughverbose, results through the returned objects, diagnostics throughlogging.tabulateis no longer a dependency output_pathoftfidf(),word2vec()andcosine_similarity(), andTextPipeline.execute(use_predefined_word_vectors=…): they were accepted but ignoredGNN(val_ratio=…): it never changed the split; set it withTextPipeline.execute(validation_ratio=…)Syntactic.constituency_parsing(),Semantic.llm()andBuilder.ConstituencyParsing, which did nothingbuilder.list_available_adjacency_methods()/list_available_builders()and the string constantsbuilder.Tfidf,builder.Word2Vec, …: use theBuilderenum.text4gcn.builder.Builderis now the same enum astext4gcn.Builder(they were two distinct classes);richis no longer a dependency- Unused internal modules (
preprocess.DataShuffler,NodeFeatureBuilder,WordProcessor,modules.utils, and empty or unreachable files); theTextPipelinecovers what they did - The empty
cpuandgpuextras:pip install text4gcninstalls PyTorch from PyPI; for another build install PyTorch first from pytorch.org utils.recommend_installation(), and thetorch_availablekey ofcheck_pytorch_installation()(PyTorch is a required dependency)data.AG_NEWS()(no longer hosted)- Unused
models.utils.get_train_test()/read_file() - The unfinished baseline models (
models/baseline: TF-IDF, LSTM)
Fixed¶
- After graph refinement, the train / test block sizes counted only the training mask, so validation documents were reported as words
- A document left empty by cleaning produced NaN node features (and a
RuntimeWarning); it now gets a zero vector - The training log's "Data statistics" table labelled the node count as "Edges"
Semantic.word2vec(num_epochs=…)was never passed to gensim, which always trained 5 epochs. Training is now also reproducible:seed(default 1234) andworkers(default 1) are new parameters, and vector initialisation no longer depends onPYTHONHASHSEED. The code behind the DCAI 2022 paper had the same bug, so its SEMANTIC results were obtained with 5 epochsSyntactic.linguistic_inquiry()looked words up in the LIWC dictionary letter by letter, so real words never matched; it now connects adjacent words that share at least one LIWC category. The code behind the DCAI 2022 paper had the same bug, so its LINGUISTIC results do not reflect LIWC categoriesSemantic.cosine_similarity(): words without a WordNet definition all received the same placeholder vector and formed a clique (99% of the word–word edges on 20 Newsgroups); they now get no word–word edgesRefinementConfig(keep_docs=True)did not protect documents frommin_degree/max_degreeRefinementConfig(keep_word_indices=…)now overrides the other word filters, as documentedRefinementConfig(top_k_edges_per_node=…)doubled the weight of edges kept by both endpoints- Ohsumed's
trainingsplit (and20news-bydate-train) was dropped by the shuffle step, which only accepted values ending intrain; split handling now follows the documented rule (train,training,20news-bydate-train→ train, anything else → test) RefinementConfig(min_doc_freq=…)/max_doc_freq_ratiocounted 0 documents for every word on CSR graphs, which removed all words (chance-level accuracy)Semantic.cosine_similarity()compares word vectors in vectorised chunks (seconds instead of hours on a 10k-word vocabulary) and returnsGraphBuildStatslike the other builders- Training on CUDA: adjacency, masks and predictions now follow the selected device
- Non-ASCII corpora:
TextPipeline(encoding=…, ascii_only=False)keeps words of any script, e.g. Arabic with diacritics (fixes #1) - Dataset downloads no longer save HTTP error pages as data
apply_refinementCSR conversion before fancy indexing (dia_matrixsafe)- Early path validation when
dataset_path/dataset_nameare missing or empty, inTextPipeline, the builders andGNN - Builders: the constructors raise
ValueErrorfor an emptydataset_nameandFileNotFoundErrorfor a missingdataset_path; every builder method raisesFileNotFoundErrorwith theTextPipelinecommand when{dataset_name}.shuffled/is missing GNN.fit()checks the adjacency file of the selected builder (ind.<builder>.<dataset>.adj), not only the.adjacency/directory, and its error names the builder call that writes it- Labels containing spaces in
.meta: label columns are split on tabs only, so a label like"real estate"is no longer truncated Syntactic.dependency_parsing()did not filter stop words: words are now read from the token indices of each dependency relation (ROOTskipped), solanguageselects the stop-word listnp.bool→bool(NumPy ≥ 1.24)
[1.0.0] — 2022-08-14¶
Added¶
- Initial public release
TextPipelinefor text preprocessing (cleaning, shuffling, node feature extraction)Frequency,Semantic,Syntacticbuilder classes for adjacency constructionGNNfor GCN training and evaluation- Support for R8, R52, 20NG, MR, Ohsumed benchmark datasets
- TF-IDF, Word2Vec, BERT, cosine similarity, dependency parsing, LIWC adjacency strategies