From a7d7f23314401be5ad057cec1d5ebc79bc34b751 Mon Sep 17 00:00:00 2001 From: pakjiddat Date: Wed, 9 Jun 2021 14:32:36 +0000 Subject: [PATCH] Improve formatting of console output - The console output needs to be formatted so it is better organized and more user friendly. The file read messages need to be in green. The file write messages need to be in blue. Each major operation should have a heading and a footer. - Correct formatting error in DESCRIPTION file. - Remove the references.bib file from .Rbuildignore. - Add headings to all major functions. - Correct error in CITATION file. - Set version number to 0.0.1 in DESCRIPTION file. - Set development mode to unreleased in pkgdown config file. - When the wordpredictor package is loaded, it shows a red warning message from the pryr package. Only the object_size method from the pryr package is used once. Instead of using this method a method may be added to the Model class for calculating the object size. The dependency on pryr package should be removed. - Build pkgdown website. --- .Rbuildignore | 1 - DESCRIPTION | 3 +- NAMESPACE | 1 - R/base.R | 51 ++- R/data-analyzer.R | 32 +- R/data-cleaner.R | 8 +- R/data-sampler.R | 40 +- R/env-manager.R | 52 ++- R/model-evaluator.R | 36 +- R/model-generator.R | 10 +- R/model-predictor.R | 2 +- R/model.R | 30 +- R/token-generator.R | 19 +- R/tp-generator.R | 28 +- README.Rmd | 4 +- README.md | 10 +- _pkgdown.yml | 2 + docs/404.html | 2 +- docs/LICENSE-text.html | 2 +- docs/LICENSE.html | 2 +- docs/articles/features.html | 361 ++++++++++++----- docs/articles/index.html | 2 +- docs/articles/overview.html | 165 +++++--- docs/authors.html | 25 +- docs/index.html | 176 ++++---- docs/news/index.html | 2 +- docs/pkgdown.yml | 2 +- docs/reference/Base.html | 217 ++++++++++ docs/reference/DataAnalyzer-1.png | Bin 0 -> 38974 bytes docs/reference/DataAnalyzer.html | 308 ++++++++++++-- docs/reference/DataCleaner.html | 158 +++++++- docs/reference/DataSampler.html | 210 ++++++++-- docs/reference/EnvManager.html | 282 +++++++++++++ docs/reference/Model.html | 74 ++-- docs/reference/ModelEvaluator-1.png | Bin 0 -> 82987 bytes docs/reference/ModelEvaluator.html | 382 ++++++++++++++++-- docs/reference/ModelGenerator.html | 143 +++++-- docs/reference/ModelPredictor.html | 287 +++++++++++-- docs/reference/Rplot001.png | Bin 0 -> 1011 bytes docs/reference/TPGenerator.html | 143 +++++-- docs/reference/TokenGenerator.html | 96 ++++- .../figures/README-analyze-ngrams-1-1.png | Bin 5593 -> 6289 bytes .../figures/README-analyze-ngrams-2-1.png | Bin 11637 -> 13111 bytes .../README-evaluate-performance-2-1.png | Bin 30671 -> 31232 bytes docs/reference/index.html | 37 +- docs/reference/wordpredictor-package.html | 200 +++++++++ docs/sitemap.xml | 10 +- inst/CITATION | 6 +- inst/examples/clean-file.R | 2 +- inst/examples/clean-lines.R | 2 +- inst/examples/compare-performance.R | 2 +- inst/examples/evaluate-performance.R | 2 +- inst/examples/extrinsic-evaluation.R | 2 +- inst/examples/generate-data.R | 2 +- inst/examples/generate-model.R | 2 +- inst/examples/generate-sample.R | 2 +- inst/examples/generate-tokens.R | 2 +- inst/examples/generate-tp.R | 2 +- inst/examples/get-file-info.R | 2 +- inst/examples/get-n-grams.R | 2 +- inst/examples/get-word-prob.R | 2 +- inst/examples/intrinsic-evaluation.R | 2 +- inst/examples/plot-n-gram-stats.R | 2 +- inst/examples/predict-word.R | 2 +- man/DataAnalyzer.Rd | 8 +- man/DataSampler.Rd | 26 +- man/EnvManager.Rd | 3 +- man/Model.Rd | 17 +- man/ModelEvaluator.Rd | 32 +- man/ModelGenerator.Rd | 2 +- man/ModelPredictor.Rd | 2 +- man/TPGenerator.Rd | 2 +- man/TokenGenerator.Rd | 2 +- man/wordpredictor-package.Rd | 8 +- tests/testthat/notes.txt | 2 +- tests/testthat/setup.R | 2 +- .../man/figures/README-tokenization-2-1.png | Bin 5554 -> 33306 bytes .../man/figures/README-tokenization-3-1.png | Bin 11542 -> 28054 bytes vignettes/references.bib | 8 + 79 files changed, 3050 insertions(+), 717 deletions(-) create mode 100644 docs/reference/Base.html create mode 100644 docs/reference/DataAnalyzer-1.png create mode 100644 docs/reference/EnvManager.html create mode 100644 docs/reference/ModelEvaluator-1.png create mode 100644 docs/reference/Rplot001.png create mode 100644 docs/reference/wordpredictor-package.html diff --git a/.Rbuildignore b/.Rbuildignore index 02b535f..6cc9759 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -12,4 +12,3 @@ ^vignettes/features_cache$ ^vignettes/overview_cache$ ^CRAN-RELEASE$ -^vignettes/references.bib$ diff --git a/DESCRIPTION b/DESCRIPTION index 7e43cd5..503e813 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: wordpredictor Title: Develop Text Prediction Models Based on N-Grams -Version: 1.0.0 +Version: 0.0.1 URL: https://github.com/pakjiddat/word-predictor, https://pakjiddat.github.io/word-predictor/ BugReports: https://github.com/pakjiddat/word-predictor/issues Authors@R: @@ -12,7 +12,6 @@ Authors@R: Description: The wordpredictor package allows developing n-gram models for text prediction. It provides methods for data cleaning, data sampling, tokenization, model generation, model evaluation and word prediction. - For information on how n-gram models work we referred to: Jurafsky, Daniel & Martin, James. (2008). "Speech and Language Processing: An Introduction to Natural Language Processing, Computational Linguistics, and Speech diff --git a/NAMESPACE b/NAMESPACE index fecf460..0e7eab1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -30,7 +30,6 @@ importFrom(ggplot2,labs) importFrom(ggplot2,xlab) importFrom(ggplot2,ylab) importFrom(patchwork,plot_annotation) -importFrom(pryr,object_size) importFrom(stringr,boundary) importFrom(stringr,str_count) importFrom(stringr,str_match) diff --git a/R/base.R b/R/base.R index 491bafb..685cfc5 100644 --- a/R/base.R +++ b/R/base.R @@ -27,7 +27,10 @@ Base <- R6::R6Class( initialize = function(fn = NULL, lc = 100, ve = 2) { # If the given file name is not NULL and is not valid if (!is.null(fn) && !file.exists(fn)) { - private$dm("The given file name is not valid", md = -1, ty = "e") + private$dm( + "The given file name is not valid", + md = -1, ty = "e" + ) } # The base class attributes are set @@ -189,8 +192,10 @@ Base <- R6::R6Class( # @param is_csv If the data is a csv file # @return The file data read_file = function(fn, is_csv) { + # The information message + msg <- paste0("Reading \033[0;", 32, "m'", fn, "'\033[0m") # Information message is shown - private$dm("Reading file:", fn, md = 1) + private$dm(msg, md = 1) # If the file is not a csv file if (!is_csv) { # File is opened for reading @@ -215,8 +220,10 @@ Base <- R6::R6Class( # @param lc The number of lines to read. # @return The file data read_lines = function(fn, lc) { + # The information message + msg <- paste0("Reading \033[0;", 32, "m'", fn, "'\033[0m") # Information message is shown - private$dm("Reading file:", fn, md = 1) + private$dm(msg, md = 1) # File is opened for reading con <- file(fn) # The file contents are read @@ -236,8 +243,10 @@ Base <- R6::R6Class( # @param fn The name of the file. # @param is_append Indicates if data should be saved. write_file = function(data, fn, is_append) { + # The information message + msg <- paste0("Writing \033[0;", 34, "m'", fn, "'\033[0m") # Information message is shown - private$dm("Saving file:", fn, md = 1) + private$dm(msg, md = 1) # If the given data is a data frame if ("data.frame" %in% class(data)) { # The data frame is written to a file @@ -264,8 +273,10 @@ Base <- R6::R6Class( # @param obj The object to save. # @param fn The file name. save_obj = function(obj, fn) { + # The information message + msg <- paste0("Writing \033[0;", 34, "m'", fn, "'\033[0m") # Information message is shown - private$dm("Saving file:", fn, md = 1) + private$dm(msg, md = 1) # The object is saved to a file in version 2 format saveRDS(obj, fn, version = 2) # The information message is shown @@ -278,8 +289,10 @@ Base <- R6::R6Class( # @param fn The file name. # @return The loaded R obj. read_obj = function(fn) { + # The information message + msg <- paste0("Reading \033[0;", 32, "m'", fn, "'\033[0m") # Information message is shown - private$dm("Reading file:", fn, md = 1) + private$dm(msg, md = 1) # If the file does not exist if (!file.exists(fn)) { # The error message @@ -321,6 +334,32 @@ Base <- R6::R6Class( } }, + # @description + # Displays the given heading text in bold. + # @param text The heading text to display. + # @param char The padding character to use. + # @param md The minimum debugging level. + # @param ll The total length of the line. Default is 80 chars. + dh = function(text, char, md, ll = 80) { + # If verbose is >= min_debug, then message is displayed + if (private$ve >= md) { + # The heading prefix + pre <- paste0(rep(char, 2), collapse = "") + pre <- paste0(pre, " ", collapse = "") + # The number of times the suffix should be repeated + c <- ll - (nchar(text) - 3) + # The heading text is added + msg <- paste0(pre, text, collapse = "") + msg <- paste0(msg, " ", collapse = "") + # The heading suffix + su <- paste0(rep(char, c), collapse = "") + msg <- paste0(msg, su, collapse = "") + msg <- paste0(msg, "\n", collapse = "") + # The heading prefix is printed + cat(msg) + } + }, + # @description # Performs processing on the data. It should be # overriden by a derived class. diff --git a/R/data-analyzer.R b/R/data-analyzer.R index 31611fc..d3bc418 100644 --- a/R/data-analyzer.R +++ b/R/data-analyzer.R @@ -1,4 +1,4 @@ -#' It allows analyzing input text files and n-gram token files +#' Analyzes input text files and n-gram token files #' #' @description #' It provides a method that returns information about text files, such as @@ -92,22 +92,18 @@ DataAnalyzer <- R6::R6Class( #' # files generated by the function can be viewed #' em$td_env() plot_n_gram_stats = function(opts) { - # The opts is merged with the da_opts attribute - private$da_opts <- modifyList(private$da_opts, opts) - # The da_opts is merged with the base class opts attribute - private$opts <- modifyList(private$opts, private$da_opts) + # The information message is shown + private$dh("Displaying Plot", "-", md = 1) # The n-gram data is read df <- private$read_obj(private$fn) - # The information message is shown - private$dm("Displaying Plot", md = 1) # If the coverage option was specified - if (private$opts[["type"]] == "coverage") { + if (opts[["type"]] == "coverage") { # The y values - y <- as.character(1:private$opts[["n"]]) + y <- as.character(1:opts[["n"]]) # The x values x <- numeric() # The percentage frequencies is calculated - for (i in 1:private$opts[["n"]]) { + for (i in 1:opts[["n"]]) { # The percentage of tokens with frequency i x[i] <- 100 * (nrow(df[df$freq == i, ]) / nrow(df)) # The percentage is rounded to 2 decimal places @@ -123,12 +119,12 @@ DataAnalyzer <- R6::R6Class( ) } # If the top_features option was specified - else if (private$opts[["type"]] == "top_features") { + else if (opts[["type"]] == "top_features") { # The plot labels labels <- list( y = "Frequency", x = "Feature", - title = paste("Top", private$opts[["n"]], "Features") + title = paste("Top", opts[["n"]], "Features") ) } # The freq column is converted to numeric @@ -138,7 +134,7 @@ DataAnalyzer <- R6::R6Class( # The data frame is sorted in descending order df <- (df[order(df$freq, decreasing = T), ]) # The top n terms are extracted - df <- df[1:private$opts[["n"]], ] + df <- df[1:opts[["n"]], ] # The chart is plotted g <- private$display_plot(df, labels) @@ -162,7 +158,7 @@ DataAnalyzer <- R6::R6Class( print(g) } # The information message is shown - private$dm(" \u2714\n", md = 1) + private$dh("DONE", "=", md = 1) return(df) }, @@ -203,6 +199,8 @@ DataAnalyzer <- R6::R6Class( #' # files generated by the function can be viewed #' em$td_env() get_file_info = function(res) { + # The information message is shown + private$dh("Generating file stats", "-", md = 1) # The list of files to check fl <- NULL # If a directory name was passed @@ -280,6 +278,9 @@ DataAnalyzer <- R6::R6Class( # The required stats stats <- list("file_stats" = fstats, "overall_stats" = ostats) + # The information message is shown + private$dh("DONE", "=", md = 1) + # The required stats are returned return(stats) }, @@ -301,7 +302,8 @@ DataAnalyzer <- R6::R6Class( #' # The name of the folder that will contain all the files. It will be #' # created in the current directory. NULL implies tempdir will be used #' fn <- NULL - #' # The required files. They are default files that are part of the package + #' # The required files. They are default files that are part of the + #' # package #' rf <- c("n2.RDS") #' # An object of class EnvManager is created #' em <- EnvManager$new(ve = ve, rp = "./") diff --git a/R/data-cleaner.R b/R/data-cleaner.R index fc0f962..9c5d5b3 100644 --- a/R/data-cleaner.R +++ b/R/data-cleaner.R @@ -130,19 +130,15 @@ DataCleaner <- R6::R6Class( #' # files generated by the function can be viewed #' em$td_env() clean_file = function() { - # The information message - msg <- paste0("Cleaning the file: ", private$fn, "\n") # The information message is shown - private$dm(msg, md = 1) + private$dh("Cleaning file", "-", md = 1) # The base class process_file function is called private$process_file( private$pre_process, private$process, private$post_process ) - # The information message - msg <- paste0("The file: ", private$fn, " has been cleaned\n") # The information message is shown - private$dm(msg, md = 1) + private$dh("DONE", "=", md = 1) # If the data should not be saved if (!private$dc_opts[["save_data"]]) { # The processed output is returned diff --git a/R/data-sampler.R b/R/data-sampler.R index fd3f581..8058759 100644 --- a/R/data-sampler.R +++ b/R/data-sampler.R @@ -1,4 +1,4 @@ -#' It allows generating data samples from text files. +#' Generates data samples from text files #' #' @description #' It provides a method for generating training, testing and validation data @@ -108,10 +108,12 @@ DataSampler <- R6::R6Class( #' # Start of environment setup code #' # The level of detail in the information messages #' ve <- 0 - #' # The name of the folder that will contain all the files. It will be created in - #' # the current directory. NULL implies tempdir will be used. + #' # The name of the folder that will contain all the files. It will be + #' # created in the current directory. NULL implies tempdir will be + #' # used #' fn <- NULL - #' # The required files. They are default files that are part of the package + #' # The required files. They are default files that are part of the + #' # package #' rf <- c("input.txt") #' # An object of class EnvManager is created #' em <- EnvManager$new(ve = ve) @@ -133,8 +135,8 @@ DataSampler <- R6::R6Class( #' ) #' ) #' - #' # The test environment is removed. Comment the below line, so the files - #' # generated by the function can be viewed + #' # The test environment is removed. Comment the below line, so the + #' # files generated by the function can be viewed #' em$td_env() generate_data = function(fn, percs) { # The directory containing the input and output files @@ -152,10 +154,11 @@ DataSampler <- R6::R6Class( if (!file.exists(fn)) { # The error message private$dm("The input file: ", - fn, - " does not exist", - md = -1, - ty = "e") + fn, + " does not exist", + md = -1, + ty = "e" + ) } # If the train, test and validation files already exist if (file.exists(paste0(dir, "/train.txt")) && @@ -196,11 +199,8 @@ DataSampler <- R6::R6Class( # The validation data is written to file private$write_file(validate_ds, paste0(dir, "/validate.txt"), F) } - # The information message - msg <- "Training, testing and validation data sets" - msg <- paste0(msg, " were successfully generated\n") # The information message is shown - private$dm(msg, md = 1) + private$dh("DONE", "=", md = 1) } ), private = list( @@ -219,9 +219,8 @@ DataSampler <- R6::R6Class( # @param dc_opts The options for cleaning the data. # @return The sampled data is returned generate_sf_from_f = function(fn = NULL, ss, ic, ir, of, is, dc_opts) { - # The information message - private$dm( - "Generating sample file from the file:", fn, "\n", md = 1) + # The information message is shown + private$dh("Generating sample file", "-", md = 1) # The input file is read data <- private$read_file(fn, F) # The number of lines in the main file @@ -258,11 +257,8 @@ DataSampler <- R6::R6Class( # The sample file is cleaned data <- dc$clean_file() } - # Information message is shown - private$dm( - "Sample file was sucessfully generated\n", - md = 1 - ) + # The information message is shown + private$dh("DONE", "=", md = 1) return(data) } diff --git a/R/env-manager.R b/R/env-manager.R index 97a0af6..1b0c79e 100644 --- a/R/env-manager.R +++ b/R/env-manager.R @@ -1,5 +1,4 @@ -#' It provides methods for setting up the test environment and reading external -#' data +#' Allows managing the test environment #' #' @description #' This class provides a method for creating directories in the tempdir folder @@ -44,10 +43,11 @@ EnvManager <- R6::R6Class( if (!file.exists(rfn)) { # An error message is shown private$dm("The file: ", - rfn, - " does not exist !", - md = -1, - ty = "e") + rfn, + " does not exist !", + md = -1, + ty = "e" + ) } } # If the file name is given but the file does not exist @@ -67,8 +67,10 @@ EnvManager <- R6::R6Class( #' Removes all files in the given directory. #' @param dn The directory name. remove_files = function(dn) { - # The information message is displayed - private$dm("Removing all files in ", dn, md = 1) + # The information message + msg <- paste0("Removing all files in ", dn, "\n") + # The information message is shown + private$dm(msg, md = 1) # Each file in the directory is deleted for (fn in dir(dn, full.names = T)) { # The file is removed @@ -85,8 +87,11 @@ EnvManager <- R6::R6Class( td_env = function(rf = F) { # The wordpredictor options wp <- getOption("wordpredictor") - # The information message is displayed - private$dm("Removing the folder: ", wp$ed, md = 1) + # The information message + msg <- paste0("Removing the folder ", wp$ed) + # The information message is shown + private$dm(msg, md = 1) + # The environment folder is removed unlink(wp$ed, recursive = T, force = T) # If the folder should not be removed @@ -110,9 +115,11 @@ EnvManager <- R6::R6Class( wp <- getOption("wordpredictor") # The path to the folder fp <- paste0(private$rp, "inst/extdata/") - # The information message is displayed - private$dm( - "Copying the directory:", wp$ed, "to the folder", fp, md = 1) + # The information message + msg <- paste0( + "Copying the directory: ", wp$ed, " to the folder ", fp) + # The information message is shown + private$dm(msg, md = 1) # If the folder does not exist if (!dir.exists(fp)) { # The new folder path is created @@ -133,12 +140,19 @@ EnvManager <- R6::R6Class( #' used to generate the environment folder. #' @return The list of folders that can be used during testing. setup_env = function(fns = c(), cf = NULL) { + # The information message + msg <- paste0("Setting up the test environment") + # The information message is shown + private$dh(msg, "-", md = 1) # The environment folder name ed <- NULL # If the cf is given and it does not exist if (!is.null(cf) && !dir.exists(cf)) { + # The information message + msg <- paste0("Creating custom environment folder: ", cf) # The information message is shown - private$dm("Creating custom environment folder:", cf, md = 1) + private$dm(msg, md = 1) + # The custom environment folder is created dir.create(cf) # The information message is shown @@ -154,7 +168,9 @@ EnvManager <- R6::R6Class( # The information message is shown private$dm( "The tempdir:", - ed, "does not exist. Creating the dir", md = 1) + ed, "does not exist. Creating the dir", + md = 1 + ) # The tempdir is created dir.create(ed) # The information message is shown @@ -169,8 +185,6 @@ EnvManager <- R6::R6Class( # The wordpredictor options are updated options("wordpredictor" = wp) - # The information message is shown - private$dm("Copying files from inst/extdata folder\n", md = 1) # Each file is copied from inst/extdata to the given folder for (fn in fns) { # The source file path @@ -180,7 +194,7 @@ EnvManager <- R6::R6Class( stop(getwd()) } # The information message is shown - private$dm("Copying file:", sfp, "to", ed, md = 1) + private$dm("Copying file:", fn, "to", ed, md = 1) # The source file is copied file.copy(sfp, ed) # The information message is shown @@ -188,7 +202,7 @@ EnvManager <- R6::R6Class( } # The information message is shown - private$dm("Environment has been built !\n", md = 1) + private$dh("DONE", "=", md = 1) return(ed) } diff --git a/R/model-evaluator.R b/R/model-evaluator.R index 6ad67d3..1b20e06 100644 --- a/R/model-evaluator.R +++ b/R/model-evaluator.R @@ -1,4 +1,4 @@ -#' It performs performance evaluation of n-gram models +#' Evaluates performance of n-gram models #' #' @description #' It provides methods for performing extrinsic and intrinsic @@ -23,7 +23,6 @@ #' percentage of correct and incorrect predictions. #' @importFrom patchwork plot_annotation #' @importFrom ggplot2 ggplot aes geom_point geom_smooth coord_cartesian labs -#' @importFrom pryr object_size ModelEvaluator <- R6::R6Class( "ModelEvaluator", inherit = Base, @@ -277,10 +276,12 @@ ModelEvaluator <- R6::R6Class( #' # The evaluation stats are printed #' print(stats) #' - #' # The test environment is removed. Comment the below line, so the files - #' # generated by the function can be viewed + #' # The test environment is removed. Comment the below line, so the + #' # files generated by the function can be viewed #' em$td_env() evaluate_performance = function(lc, fn) { + # The information message is shown + private$dh("Evaluating model performance", "-", md = 1) # The Model class object is fetched m <- private$mp$get_model() # The performance stats @@ -294,7 +295,7 @@ ModelEvaluator <- R6::R6Class( }) # The y-axis values are updated - pstats[["m"]] <- as.numeric(object_size(m)) + pstats[["m"]] <- m$get_size() pstats[["t"]] <- tt[[3]] pstats[["p"]] <- istats$mean pstats[["a"]] <- estats$valid_perc @@ -305,6 +306,8 @@ ModelEvaluator <- R6::R6Class( private$dm("Saving stats to model file\n", md = 1) # The model is saved private$save_obj(m, private$mf) + # The information message is shown + private$dh("DONE", "=", md = 1) # The performance stats are returned return(pstats) }, @@ -326,7 +329,8 @@ ModelEvaluator <- R6::R6Class( #' # The name of the folder that will contain all the files. It will be #' # created in the current directory. NULL implies tempdir will be used #' fn <- NULL - #' # The required files. They are default files that are part of the package + #' # The required files. They are default files that are part of the + #' # package #' rf <- c("def-model.RDS", "validate-clean.txt") #' # An object of class EnvManager is created #' em <- EnvManager$new(ve = ve, rp = "./") @@ -346,10 +350,12 @@ ModelEvaluator <- R6::R6Class( #' # The evaluation stats are printed #' print(stats) #' - #' # The test environment is removed. Comment the below line, so the files - #' # generated by the function can be viewed + #' # The test environment is removed. Comment the below line, so the + #' # files generated by the function can be viewed #' em$td_env() intrinsic_evaluation = function(lc, fn) { + # The information message is shown + private$dh("Performing intrinsic evaluation", "-", md = 1) # The validation data is read data <- private$read_lines(fn, lc) # The list of perplexities @@ -364,7 +370,8 @@ ModelEvaluator <- R6::R6Class( p <- private$mp$calc_perplexity(words) # The information message msg <- paste0( - "Perplexity of the sentence '", line, "' is: ", p, "\n") + "Perplexity of the sentence '", line, "' is: ", p, "\n" + ) # The information message is shown private$dm(msg, md = 2) # The list of perplexities is updated @@ -383,6 +390,8 @@ ModelEvaluator <- R6::R6Class( "max" = max(pl), "mean" = mean(pl) ) + # The information message is shown + private$dh("DONE", "=", md = 1) return(stats) }, @@ -427,10 +436,12 @@ ModelEvaluator <- R6::R6Class( #' # The evaluation stats are printed #' print(stats) #' - #' # The test environment is removed. Comment the below line, so the files - #' # generated by the function can be viewed + #' # The test environment is removed. Comment the below line, so the + #' # files generated by the function can be viewed #' em$td_env() extrinsic_evaluation = function(lc, fn) { + # The information message is shown + private$dh("Performing extrinsic evaluation", "-", md = 1) # The Model class object is fetched m <- private$mp$get_model() # The TokenGenerator object options @@ -497,6 +508,9 @@ ModelEvaluator <- R6::R6Class( # The precentage of invalid stats[["invalid_perc"]] <- 100 - stats[["valid_perc"]] + # The information message is shown + private$dh("DONE", "=", md = 1) + return(stats) } ), diff --git a/R/model-generator.R b/R/model-generator.R index 37071eb..2004fc2 100644 --- a/R/model-generator.R +++ b/R/model-generator.R @@ -1,4 +1,4 @@ -#' It is used to generate n-gram models from a text file +#' Generates n-gram models from a text file #' #' @description #' It provides a method for generating n-gram models. The n-gram models may be @@ -100,7 +100,7 @@ ModelGenerator <- R6::R6Class( #' em$td_env() generate_model = function() { # The information message is displayed - private$dm("Generating n-gram model\n", md = 1) + private$dh("Generating n-gram model", "-", md = 1) # The cleaned sample data file is generated private$generate_sample() # The data files are generated @@ -111,8 +111,8 @@ ModelGenerator <- R6::R6Class( private$generate_tp_data() # The model is saved private$save_model() - # The information message is displayed - private$dm("N-gram model was successfully generated\n", md = 1) + # The information message is shown + private$dh("DONE", "=", md = 1) } ), private = list( @@ -129,7 +129,7 @@ ModelGenerator <- R6::R6Class( # The output file path ofp <- paste0(dir, "/", ofn) # The information message is shown - private$dm("Saving model\n", md = 1) + private$dh("Saving model", "-", md = 1) # The model object is loaded private$m$load_model() # The model object is saved to the models folder using the output diff --git a/R/model-predictor.R b/R/model-predictor.R index 57a732b..428e6dd 100644 --- a/R/model-predictor.R +++ b/R/model-predictor.R @@ -1,4 +1,4 @@ -#' It allows predicting text, calculating word probabilities and Perplexity +#' Allows predicting text, calculating word probabilities and Perplexity #' #' @description #' It provides a method for predicting the new word given a set of diff --git a/R/model.R b/R/model.R index a7917c9..bcc3ae3 100644 --- a/R/model.R +++ b/R/model.R @@ -1,4 +1,4 @@ -#' This class represents n-gram models +#' Represents n-gram models #' #' @description #' The Model class represents n-gram models. An instance of the class is a @@ -74,7 +74,9 @@ Model <- R6::R6Class( # If the directory does not exist, then an error is thrown if (!dir.exists(dir)) { private$dm( - "The dir: ", dir, " does not exist !", md = -1, ty = "e") + "The dir: ", dir, " does not exist !", + md = -1, ty = "e" + ) } # An object of class EnvManager is created @@ -126,7 +128,7 @@ Model <- R6::R6Class( # The file contents dict <- private$read_file(fn, F) # The information message is shown - private$dm("Calculating default probability\n", md = 1) + private$dh("Calculating default probability", "-", md = 1) # The number of words in the dictionary file. It is used to # calculate Perplexity. vc <- length(dict) @@ -142,6 +144,8 @@ Model <- R6::R6Class( n <- length(w) # The default probability is set private$dp <- 1 / (n + vc) + # The information message is shown + private$dh("DONE", "=", md = 1) }, #' @description @@ -153,6 +157,26 @@ Model <- R6::R6Class( cv <- private[[cn]] return(cv) + }, + + #' @description + #' It returns the size of the current object. The object + #' size is calculated as the sum of sizes of the object attributes. + #' @return The size of the object in bytes. + get_size = function() { + # The required object size + s <- 0 + # The tp size is added + s <- s + as.numeric(object.size(private$tp)) + # The wl size is added + s <- s + as.numeric(object.size(private$wl)) + # The dc_opts size is added + s <- s + as.numeric(object.size(private$dc_opts)) + # The tg_opts size is added + s <- s + as.numeric(object.size(private$tg_opts)) + # The pstats size is added + s <- s + as.numeric(object.size(self$pstats)) + return(s) } ), private = list( diff --git a/R/token-generator.R b/R/token-generator.R index a0d94a3..81213c9 100644 --- a/R/token-generator.R +++ b/R/token-generator.R @@ -1,4 +1,4 @@ -#' It generates n-grams of given size from an input text file +#' Generates n-grams from text files #' #' @description #' It generates n-gram tokens along with their frequencies. The data @@ -97,19 +97,16 @@ TokenGenerator <- R6::R6Class( else { # The information message msg <- paste0("Generating ", private$tg_opts[["n"]]) - msg <- paste0(msg, "-gram tokens\n") + msg <- paste0(msg, "-gram tokens") # The information message is shown - private$dm(msg, md = 1) + private$dh(msg, "-", md = 1) # The base class process_file function is called private$process_file( private$pre_process, private$process, private$post_process ) - # The information message - msg <- paste0(private$tg_opts[["n"]], "-gram tokens") - msg <- paste0(msg, " were successfully generated\n") # The information message is shown - private$dm(msg, md = 1) + private$dh("DONE", "=", md = 1) } } ), @@ -174,7 +171,7 @@ TokenGenerator <- R6::R6Class( post_process = function() { # The information message msg <- paste0("Calculating ", private$tg_opts[["n"]]) - msg <- paste0(msg, "-gram frequencies\n") + msg <- paste0(msg, "-gram frequencies") # The information message is shown private$dm(msg, md = 1) # The output is copied to a variable @@ -185,6 +182,8 @@ TokenGenerator <- R6::R6Class( df <- df %>% group_by(pre) %>% summarize_all(sum) + # The information message is shown + private$dm(" \u2714\n", md = 1) # If the minimum n-gram frequency is given if (private$tg_opts[["min_freq"]] > -1) { # The information message is shown @@ -198,10 +197,6 @@ TokenGenerator <- R6::R6Class( colnames(df) <- c("pre", "freq") # The output is set to the updated variable private$p_output <- df - # The information message - msg <- paste0(private$tg_opts[["n"]], "-gram frequencies") - # The information message is shown - private$dm(msg, "were sucessfully calculated\n", md = 1) # If the n-gram data should be saved if (private$tg_opts[["save_ngrams"]]) { # The required file name diff --git a/R/tp-generator.R b/R/tp-generator.R index e369cd5..61a5024 100644 --- a/R/tp-generator.R +++ b/R/tp-generator.R @@ -1,4 +1,4 @@ -#' It is used to generate transition probabilities for n-grams. +#' Generates transition probabilities for n-grams #' #' @description #' It provides a method for generating transition probabilities for @@ -93,9 +93,9 @@ TPGenerator <- R6::R6Class( generate_tp = function() { # The information message msg <- paste0("Generating Transition Probabilities for n = ") - msg <- paste0(msg, "1:", private$tp_opts[["n"]], "\n") + msg <- paste0(msg, "1:", private$tp_opts[["n"]]) # Information message is shown - private$dm(msg, md = 1) + private$dh(msg, "-", md = 1) # The processed output is cleared private$p_output <- data.frame() # The output format @@ -138,12 +138,8 @@ TPGenerator <- R6::R6Class( tp_opts$n <- n # The transition probabilities or word list is generated self$generate_tp_for_n(n) - # If n == 1, then word list data is saved - if (n == 1) { - # The combined tp data is saved - private$save_data() - } - else { + # If n > 1 + if (n > 1) { # c_pre is updated c_pre <- c(c_pre, private$p_output$pre) # c_nw is updated @@ -167,10 +163,7 @@ TPGenerator <- R6::R6Class( private$save_data(fn) } # Information message is shown - private$dm( - "Sucessfully generated transition probabilities\n", - md = 1 - ) + private$dh("DONE", "=", md = 1) } }, @@ -213,9 +206,9 @@ TPGenerator <- R6::R6Class( else { # The information message msg <- paste0( - "Generating transition probabilities for n=", n, "\n") + "Generating transition probabilities for n = ", n) # Information message is shown - private$dm(msg, md = 1) + private$dh(msg, "-", md = 1) # The input file name private$fn <- private$get_file_name(F) @@ -260,11 +253,8 @@ TPGenerator <- R6::R6Class( if (private$tp_opts[["save_tp"]]) { private$save_data() } - # The information message - msg <- paste0("Successfully generated transition") - msg <- paste0(msg, " probabilities for n=", n, "\n") # Information message is shown - private$dm(msg, md = 1) + private$dh("DONE", "=", md = 1) } } ), diff --git a/README.Rmd b/README.Rmd index 0d02693..62afa9d 100644 --- a/README.Rmd +++ b/README.Rmd @@ -83,9 +83,7 @@ The **wordpredictor** package is based on **R6 classes**. It is easy to customiz 8. **ModelPredictor**. It allows predicting the next word, given a set of previous words. 9. **TextFileProcessor**. The base class for all the other classes. It allows provides methods for reading and writing files and processing large text files. -Information about the package can be obtained using the command line or the package website. For example, the command: **?wordpredictor::class-name** returns information about how the given class words and the parameter details for each class method. - -The [package website](https://pakjiddat.github.io/word-predictor/) provides information about each class and its methods. It also provides information about how the package works. +Information about the package can be obtained using the command line or the package website. For example, the command: **?wordpredictor** returns information about how the given class works and the parameter details for each class method. ## Generating the model diff --git a/README.md b/README.md index c767b41..f927b41 100644 --- a/README.md +++ b/README.md @@ -64,13 +64,9 @@ customize and improve. It provides the following classes: large text files. Information about the package can be obtained using the command line or -the package website. For example, the command: -**?wordpredictor::class-name** returns information about how the given -class words and the parameter details for each class method. - -The [package website](https://pakjiddat.github.io/word-predictor/) -provides information about each class and its methods. It also provides -information about how the package works. +the package website. For example, the command: **?wordpredictor** +returns information about how the given class works and the parameter +details for each class method. ## Generating the model diff --git a/_pkgdown.yml b/_pkgdown.yml index f9d154f..a81b332 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -1,2 +1,4 @@ url: https://pakjiddat.github.io/word-predictor title: Word Predictor +development: + mode: unreleased diff --git a/docs/404.html b/docs/404.html index 01c9dc3..47b52e9 100644 --- a/docs/404.html +++ b/docs/404.html @@ -71,7 +71,7 @@ Word Predictor - 1.0.0 + 0.0.1 diff --git a/docs/LICENSE-text.html b/docs/LICENSE-text.html index ea2572c..fe4ef29 100644 --- a/docs/LICENSE-text.html +++ b/docs/LICENSE-text.html @@ -71,7 +71,7 @@ Word Predictor - 1.0.0 + 0.0.1 diff --git a/docs/LICENSE.html b/docs/LICENSE.html index 561bae5..11de798 100644 --- a/docs/LICENSE.html +++ b/docs/LICENSE.html @@ -71,7 +71,7 @@ Word Predictor - 1.0.0 + 0.0.1 diff --git a/docs/articles/features.html b/docs/articles/features.html index 49d54e0..b8be4ac 100644 --- a/docs/articles/features.html +++ b/docs/articles/features.html @@ -31,7 +31,7 @@ Word Predictor - 1.0.0 + 0.0.1 @@ -98,7 +98,7 @@

features

Introduction

-

This document describes all the features provided by the wordpredictor package. It first describes how to generate n-gram models. Next it describes how to evaluate the performance of the model. Finally it describes how to make word predictions using the model.

+

This document describes all the features provided by the wordpredictor package. It first describes how to generate n-gram models. Next it describes how to evaluate the performance of the n-gram models. Finally it describes how to make word predictions using the n-gram model.

@@ -111,31 +111,35 @@

The first step in generating a n-gram model is data exploration. This involves determining the type of textual content and various text related statistics. The type of text may be news content, blog posts, Twitter feeds, product reviews, customer chat history etc. Example of text related statistics are line count, word count, average line length and input file size.

It is also important to determine the unwanted words and symbols in the data such as vulgar words, punctuation symbols, non-alphabetical symbols etc. The wordpredictor package provides the DataAnalyzer class which can be used to find out statistics about the input data. The following example shows how to get statistics on all text files within a folder:

-# The DataAnalyzer object is created
-da <- DataAnalyzer$new()
+# The required files
+rf <- c(
+  "test.txt",
+  "validate.txt",
+  "validate-clean.txt",
+  "test-clean.txt"
+)
+# The test environment is setup
+ed <- setup_env(rf, ve)
+
+# The DataAnalyzer object is created
+da <- DataAnalyzer$new(ve = ve)
 # Information on all text files in the ddir2 folder is returned
-fi <- da$get_file_info(ddir2)
+fi <- da$get_file_info(ed)
 # The file information is printed
 print(fi)
 #> $file_stats
-#>                                           fn total_lc max_ll min_ll mean_ll
-#> 1          ../tests/testthat/data2/input.txt    51236   5883      2     229
-#> 2     ../tests/testthat/data2/test-clean.txt    12562    820      4      82
-#> 3           ../tests/testthat/data2/test.txt     5125   2508      2     230
-#> 4          ../tests/testthat/data2/train.txt    40989   5883      2     229
-#> 5 ../tests/testthat/data2/validate-clean.txt    12320    681      4      83
-#> 6       ../tests/testthat/data2/validate.txt     5125   2061      2     226
-#>        size
-#> 1   11.3 Mb
-#> 2 1022.1 Kb
-#> 3    1.1 Mb
-#> 4      9 Mb
-#> 5 1007.5 Kb
-#> 6    1.1 Mb
+#>                                   fn total_lc max_ll min_ll mean_ll   size
+#> 1     /tmp/RtmpFEVdE7/test-clean.txt       73     50     25      39 2.8 Kb
+#> 2           /tmp/RtmpFEVdE7/test.txt       73     51     28      41   3 Kb
+#> 3 /tmp/RtmpFEVdE7/validate-clean.txt       75     48     10      37 2.8 Kb
+#> 4       /tmp/RtmpFEVdE7/validate.txt       73     50     31      40 2.9 Kb
 #> 
 #> $overall_stats
 #>   total_lc max_ll min_ll mean_ll total_s
-#> 1   127357   5883      4     230 24.6 Mb
+#> 1 294 51 31 41 11.5 Kb + +# The test envionment is cleaned up +clean_up(ve)

The word count of a text file can be fetched using the command: cat file-name | wc -w. This command should work on all Unix based systems.

@@ -144,17 +148,22 @@

The next step is to generate training, testing and validation samples from the input text file. If there are many input text files, then they can be combined to a single file using the command: cat file-1 file-2 file3 > output-file. The contents of the combined text file may need to be randomized.

The wordpredictor package provides the DataSampler class which can be used to generate a random sample containing given number of lines. The following example shows how to generate a random sample of size 10 Mb from an input text file:

-# The sample size in Mb
-ssize <- 10
+# The required files
+rf <- c("input.txt")
+# The test environment is setup
+ed <- setup_env(rf, ve)
+
+# The sample size as a proportion of the input.txt file
+ssize <- 0.1
 # The data file path
-dfp <- paste0(ddir2, "/input.txt")
+dfp <- paste0(ed, "/input.txt")
 
 # The object size is formatted
 obj_size <- file.size(dfp)/10^6
 # The proportion of data to sample
 prop <- (ssize/obj_size)
 # An object of class DataSampler is created
-ds <- DataSampler$new(ddir = ddir2, mdir = ddir2)
+ds <- DataSampler$new(dir = ed, ve = ve)
 # The sample file is generated.
 # The randomized sample is saved to the file train.txt in the ddir
 ds$generate_sample(
@@ -164,32 +173,49 @@ 

ir = T, ofn = "train.txt", is = T -)

+) + +# The test envionment is cleaned up +clean_up(ve)

Usually we need a train data set for generating the n-gram model. A test data set for testing the model and a validation data set for evaluating the performance of the model. The following example shows how to generate the train, test and validation files. The train file contains the first 80% of the lines, the test set contains the next 10% of the lines. The remaining lines are in the validation set.

The data in the validation file must be different from the data in the train file. Otherwise it can result in over-fitting of the model. When a model is over-fitted, the model evaluation results will be exaggerated, overly optimistic and unreliable. So care should be taken to ensure that the data in the validation and train files is different.

-# An object of class DataSampler is created
-ds <- DataSampler$new(ddir = ddir2, mdir = ddir2)
+# The required files
+rf <- c("input.txt")
+# The test environment is setup
+ed <- setup_env(rf, ve)
+
+# An object of class DataSampler is created
+ds <- DataSampler$new(dir = ed, ve = ve)
 # The train, test and validation files are generated
 ds$generate_data(
     fn =  "input.txt",
-    dir = ddir2,
     percs = list(
         "train" = 0.8,
         "test" = 0.1,
         "validate" = 0.1
     )
-)
+) + +# The test envionment is cleaned up +clean_up(ve)

In the above example, ddir parameter is the data directory containing the input.txt file. mdir parameter is the output directory that will contain the generated test, validation and train data files.

Data Cleaning

-

The next step is to remove unwanted symbols and words from the input text file. This reduces the memory requirement of the n-gram model and makes it more efficient. Example of unwanted words are vulgar words and words that are not part of the vocabulary. Punctuation, numbers, non-printable characters, extra spaces may also be removed.

+

The next step is to remove unwanted symbols and words from the input text file. This reduces the memory requirement of the n-gram model and makes it more efficient. Example of unwanted words are vulgar words, words that are not part of the vocabulary, punctuation, numbers, non-printable characters and extra spaces.

The wordpredictor package provides the DataCleaner class which can be used to remove unwanted words and symbols from text files. The following example shows how to clean a given text file:

-# The data file path
-fn <- paste0(ddir2, "/input.txt")
+# The required files
+rf <- c("input.txt")
+# The test environment is setup
+ed <- setup_env(rf, ve)
+
+# The data file path
+fn <- paste0(ed, "/input.txt")
+# The clean file path
+cfn <- paste0(ed, "/input-clean.txt")
 # The data cleaning options
 dc_opts = list(
     "min_words" = 2,
@@ -199,111 +225,197 @@ 

"remove_non_dict" = T, "remove_non_alpha" = T, "remove_extra_space" = T, - "remove_bad" = F + "remove_bad" = F, + "output_file" = cfn ) # The data cleaner object is created -dc <- DataCleaner$new(fn, dc_opts) +dc <- DataCleaner$new(fn, dc_opts, ve = ve) # The sample file is cleaned and saved as input-clean.txt in the ddir -dc$clean_file()

-

The clean_file method reads a certain number of lines at a time, cleans the lines of text and saves them to an output text file. It is useful for cleaning large text files.

+dc$clean_file() + +# The test envionment is cleaned up +clean_up(ve)
+

The clean_file method reads a certain number of lines at a time, cleans the lines of text and saves them to an output text file. It can be used for cleaning large text files.

Tokenization

The next step is to generate n-gram tokens from the cleaned text file. The TokenGenerator class allows generating n-gram tokens of given size from a given input text file. The following example shows how to generate n-grams tokens of size 1,2,3 and 4:

-# The test file path
-fn <- paste0(ddir2, "/test.txt")
+# The required files
+rf <- c("test-clean.txt")
+# The test environment is setup
+ed <- setup_env(rf, ve)
+
+# The test file path
+fn <- paste0(ed, "/test-clean.txt")
 # The n-grams are generated
 for (n in 1:4) {
   # The ngram number is set
-  tg_opts = list("n" = n, "save_ngrams" = T, dir = ddir2)
+  tg_opts = list("n" = n, "save_ngrams" = T, dir = ed)
   # The TokenGenerator object is created
-  tg <- TokenGenerator$new(fn, tg_opts)
+  tg <- TokenGenerator$new(fn, tg_opts, ve = ve)
   # The ngram tokens are generated
   tg$generate_tokens()
-}
-

The above code generates the files n1.RDS, n2.RDS, n3.RDS and n4.RDS in the data directory. This files contains n-gram tokens along with their frequencies. N-grams of larger size provide more context. Usually n-grams of size 4 are generated.

-

Two important customization options supported by the TokenGenerator class are min_freq and stem_words. min_freq sets minimum frequency for n-gram tokens. All n-gram tokens with frequency less than min_freq are excluded. When the stem_words option is used, n-gram prefix components are stemmed. The next word is not transformed.

-

The n-gram token frequencies may be analyzed using the DataAnalyzer class. The following example displays the top most occurring n-gram tokens:

+} + +# The test envionment is cleaned up +clean_up(ve)
+

The above code generates the files n1.RDS, n2.RDS, n3.RDS and n4.RDS in the data directory. These files contains n-gram tokens along with their frequencies. N-grams of larger size provide more context. Usually n-grams of size 4 are generated.

+

Two important customization options supported by the TokenGenerator class are min_freq and stem_words. min_freq sets minimum frequency for n-gram tokens. All n-gram tokens with frequency less than min_freq are excluded.

+

The stem_words option is used to transform n-gram prefix components to their stems. The next word is not transformed.

+

The n-gram token frequencies may be analyzed using the DataAnalyzer class. The following example displays the top most occurring 2-gram tokens:

-# The ngram file name
-fn <- paste0(ddir2, "/n3.RDS")
+# The required files
+rf <- c("n2.RDS")
+# The test environment is setup
+ed <- setup_env(rf, ve)
+
+# The ngram file name
+fn <- paste0(ed, "/n2.RDS")
 # The DataAnalyzer object is created
-da <- DataAnalyzer$new(fn)
+da <- DataAnalyzer$new(fn, ve = ve)
 # The top features plot is checked
 df <- da$plot_n_gram_stats(opts = list(
     "type" = "top_features",
     "n" = 10,
     "save_to" = NULL,
-    "dir" = sdir
+    "dir" = ed
 ))

-

The following example shows the distribution of word frequencies:

-# The ngram file name
-fn <- paste0(ddir2, "/n3.RDS")
+
+# The test envionment is cleaned up
+clean_up(ve)
+

The following example shows the distribution of word frequencies:

+
+# The required files
+rf <- c("n2.RDS")
+# The test environment is setup
+ed <- setup_env(rf, ve)
+
+# The ngram file name
+fn <- paste0(ed, "/n2.RDS")
 # The DataAnalyzer object is created
-da <- DataAnalyzer$new(fn)
+da <- DataAnalyzer$new(fn, ve = ve)
 # The top features plot is checked
 df <- da$plot_n_gram_stats(opts = list(
     "type" = "coverage",
     "n" = 10,
     "save_to" = NULL,
-    "dir" = sdir
+    "dir" = ed
 ))

-

The following example returns all 3-gram tokens that start with great_:

-
-# The ngram file name
-fn <- paste0(ddir2, "/n3.RDS")
+
+
+# The test envionment is cleaned up
+clean_up(ve)
+

The following example returns top 10 2-gram tokens that start with and_:

+
+# The required files
+rf <- c("n2.RDS")
+# The test environment is setup
+ed <- setup_env(rf, ve)
+
+# The ngram file name
+fn <- paste0(ed, "/n2.RDS")
 # The DataAnalyzer object is created
-da <- DataAnalyzer$new()
-# Bi-grams starting with "great_" are returned
-df <- da$get_ngrams(fn = fn, c = 10, pre = "^great_*")
+da <- DataAnalyzer$new(ve = ve)
+# Bi-grams starting with "and_" are returned
+df <- da$get_ngrams(fn = fn, c = 10, pre = "^and_*")
 # The data frame is sorted by frequency
 df <- df[order(df$freq, decreasing = T),]
-# The data frame is printed
-print(df)
-#> # A tibble: 140 x 2
-#>    pre                      freq
-#>    <fct>                   <dbl>
-#>  1 great_from_the              2
-#>  2 great_idea_to               2
-#>  3 great_to_be                 2
-#>  4 great_to_see                2
-#>  5 greater_than_our            2
-#>  6 great_ability_to            1
-#>  7 great_about_is              1
-#>  8 great_accidents_that        1
-#>  9 great_ambassadors_and       1
-#> 10 great_american_festival     1
-#> # … with 130 more rows
+# The first 10 rows of the data frame are printed +knitr::kable(df[1:10,], col.names = c("Prefix", "Frequency"))
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PrefixFrequency
and_the2
and_cart1
and_fired1
and_forget1
and_leave1
and_open1
and_out1
and_phrase1
and_say1
and_tea1
+
+
+# The test envionment is cleaned up
+clean_up(ve)

Transition Probabilities

-

The next step in generating the n-gram model is to generate transition probabilities from the n-gram files. The TPGenerator class is used to generate the transition probabilities. For each n-gram token file a corresponding transition probabilities file is generated.

-

The transition probabilities files are then combined into a single file containing transition probability data for n-grams of size 1, 2, 3, 4 etc.

-

The following example shows how to generate combined transition probabilities for n-grams of size 1, 2, 3 and 4:

-
-# The TPGenerator object is created
-tp <- TPGenerator$new(opts = list(n = 4, dir = ddir2))
+

The next step in generating the n-gram model is to generate transition probabilities (tp) from the n-gram files. The TPGenerator class is used to generate the tps. For each n-gram token file a corresponding tp file is generated.

+

The tp files are then combined into a single file containing tp data for n-grams of size 1, 2, 3, 4 etc.

+

The following example shows how to generate combined tps for n-grams of size 1, 2, 3 and 4:

+
+# The required files
+rf <- c("n1.RDS", "n2.RDS", "n3.RDS", "n4.RDS")
+# The test environment is setup
+ed <- setup_env(rf, ve)
+# The TPGenerator object is created
+tp <- TPGenerator$new(opts = list(n = 4, dir = ed), ve = ve)
 # The combined transition probabilities are generated
-tp$generate_tp()
-

The above code produces the final model-4.RDS.

+tp$generate_tp() + +# The test envionment is cleaned up +clean_up(ve)
+

The above code produces the file model-4.RDS.

The model file

-

The final step is to generate a n-gram model file from the files generated in the previous steps. The Model class contains the method load_model, which reads the combined transition probabilities files and other files that are used by the model. An instance of the Model class represents the n-gram model.

+

The final step is to generate a n-gram model file from the files generated in the previous steps. The Model class contains the method load_model, which reads the combined tps files and other files that are used by the model. An instance of the Model class represents the n-gram model.

Generating the model in one step

All the previous steps may be combined into a single step. The ModelGenerator class allows generating the final n-gram model using a single method call. The following example generates a n-gram model using default data cleaning and tokenization options:

-
-# The following code generates n-gram model using default options for data
+
+# The required files
+rf <- c("input.txt")
+# The test environment is setup
+ed <- setup_env(rf, ve)
+
+# The following code generates n-gram model using default options for data
 # cleaning and tokenization. See the following section on how to customize these
 # options. Note that input.txt is the name of the input data file. It should be
 # present in the data directory. ddir is the data directory. mdir is the model
@@ -317,17 +429,18 @@ 

fn = "def-model.RDS", df = "input.txt", n = 4, - ssize = 10, - ddir = ddir1, - mdir = mdir, + ssize = 0.1, + dir = ed, dc_opts = list(), tg_opts = list(), - ve = 2 + ve = ve ) -# Generates n-gram model. The output is the file -# ./data/model/def-model.RDS -mg$generate_model()

+# Generates n-gram model. The output is the file def-model.RDS +mg$generate_model() + +# The test envionment is cleaned up +clean_up(ve)
@@ -335,39 +448,63 @@

Evaluating the model performance

The wordpredictor package provides the ModelEvaluator class for evaluating the performance of the generated n-gram model. Intrinsic and Extrinsic evaluation are supported. Also the performance of several n-gram models may be compared.

The following example performs Intrinsic evaluation. It measures the Perplexity score for each sentence in the validation.txt file, that was generated in the data sampling step. It returns the minimum, mean and maximum Perplexity score for each line.

-
-# The model file name
-mfn <- paste0(mdir, "/def-model.RDS")
+
+# The required files
+rf <- c("def-model.RDS", "validate-clean.txt")
+# The test environment is setup
+ed <- setup_env(rf, ve)
+
+# The model file name
+mfn <- paste0(ed, "/def-model.RDS")
 # The path to the cleaned validation file
-vfn <- paste0(mdir, "/validate.txt")
+vfn <- paste0(ed, "/validate-clean.txt")
 # ModelEvaluator class object is created
-me <- ModelEvaluator$new(mf = mfn)
+me <- ModelEvaluator$new(mf = mfn, ve = ve)
 # The intrinsic evaluation is performed on first 20 lines
-stats <- me$intrinsic_evaluation(lc = 20, fn = vfn)
-

The following example performs Extrinsic evaluation. It measures the accuracy score for each sentence in validation.txt file. For each sentence the model is used to predict the last word in the sentence given the previous words. If the last word was correctly predicted, then the prediction is considered to be accurate. The Extrinsic evaluation returns the number of correct and incorrect predictions.

-
-# The model file name
-mfn <- paste0(mdir, "/def-model.RDS")
+stats <- me$intrinsic_evaluation(lc = 20, fn = vfn)
+
+# The test envionment is cleaned up
+clean_up(ve)
+

The following example performs Extrinsic evaluation. It measures the accuracy score for each sentence in validation.txt file. For each sentence the model is used to predict the last word in the sentence given the previous words. If the last word was correctly predicted, then the prediction is considered to be accurate.

+
+# The required files
+rf <- c("def-model.RDS", "validate-clean.txt")
+# The test environment is setup
+ed <- setup_env(rf, ve)
+
+# The model file name
+mfn <- paste0(ed, "/def-model.RDS")
 # The path to the cleaned validation file
-vfn <- paste0(mdir, "/validate.txt")
+vfn <- paste0(ed, "/validate-clean.txt")
 # ModelEvaluator class object is created
-me <- ModelEvaluator$new(mf = mfn)
+me <- ModelEvaluator$new(mf = mfn, ve = ve)
 # The intrinsic evaluation is performed on first 100 lines
-stats <- me$extrinsic_evaluation(lc = 100, fn = vfn)
+stats <- me$extrinsic_evaluation(lc = 100, fn = vfn) + +# The test envionment is cleaned up +clean_up(ve)

Making word predictions

The n-gram model generated in the previous step can be used to predict the next word given a set of words. The following example shows how to predict the next word. It returns the 3 possible next words along with their probabilities.

-
-# The model file name
-mfn <- paste0(mdir, "/def-model.RDS")
+
+# The required files
+rf <- c("def-model.RDS")
+# The test environment is setup
+ed <- setup_env(rf, ve)
+
+# The model file name
+mfn <- paste0(ed, "/def-model.RDS")
 # An object of class ModelPredictor is created. The mf parameter is the name of
 # the model file that was generated in the previous example.
-mp <- ModelPredictor$new(mf = mfn)
+mp <- ModelPredictor$new(mf = mfn, ve = ve)
 # Given the words: "how are", the next word is predicted. The top 3 most likely
 # next words are returned along with their respective probabilities.
-res <- mp$predict_word(words = "how are", 3)
+res <- mp$predict_word(words = "how are", 3) + +# The test envionment is cleaned up +clean_up(ve)
diff --git a/docs/articles/index.html b/docs/articles/index.html index 7b92906..85fed3f 100644 --- a/docs/articles/index.html +++ b/docs/articles/index.html @@ -71,7 +71,7 @@ Word Predictor - 1.0.0 + 0.0.1 diff --git a/docs/articles/overview.html b/docs/articles/overview.html index cf05f4c..b649868 100644 --- a/docs/articles/overview.html +++ b/docs/articles/overview.html @@ -31,7 +31,7 @@ Word Predictor - 1.0.0 + 0.0.1 @@ -111,9 +111,10 @@

The n-gram model is generated in steps. In the first step, the input data is cleaned. Unwanted symbols and words are removed from the input data.

In the next step, the cleaned data file is read. N-grams are extracted from the file, starting from 1-grams up to the configured n-gram size. The 1-gram, 2-gram, 3-gram etc tokens are saved in separate files along with the frequency. So the 3-gram file contains all extracted 3-grams and their respective frequencies.

The next step is to generate transition probability tables for each n-gram file. For the 1-gram file the transition probability table is simply the list of unique words along with the word frequencies. For the other n-gram files, the transition probability table is a data frame with 3 columns. The hash of n-gram prefixes, the next word id and the next word probability.

-

The n-gram prefix is the set of n-1 components before the last component. The n-1 components are combined using "_" and converted to a numeric hash value using the digest2Int method of the digest package.

+

The n-gram prefix is the set of n-1 components before the last component. The n-1 components are combined using "_" and converted to a numeric hash value using the digest2Int method of the digest package.

The next word id is the numeric index of the next word in the list of 1-grams. The next word probability is the probability of the next word given the previous n-1 words. It is calculated using Maximum Likelihood Estimation (MLE) as described above.

-

Instead of storing the n-gram prefix strings, a single number is saved. Also instead of storing the next word, the numeric index of the next word is saved. This saves a lot of memory and allows more data to be stored, which improves the n-gram model’s efficiency. In R, a number requires a fixed amount of storage, which about 56 bytes. In contrast the memory required to store a string increases with the string size.

+

Instead of storing the n-gram prefix strings, a single number is saved. Also instead of storing the next word, the numeric index of the next word is saved. This saves a lot of memory and allows more data to be stored, which improves the n-gram model’s efficiency.

+

In R, a number requires a fixed amount of storage, which about 56 bytes. In contrast the memory required to store a string increases with the number of characters in the string.

The data frames that represent each transition probability table are combined into a single data frame. The combined transition probability table is used to make word predictions.

@@ -121,7 +122,7 @@

Using the model to predict words

To predict a word, the word along with the n-1 previous words are used as input. The model computes the hash of the previous words and looks up the hash in the combined transition probabilities table. If the hash was found, then the model extracts the top 3 next word ids that have the highest probabilities.

The model looks up the next word text that corresponds to the next word ids. The result is the top 3 most likely next words along with their probabilities.

-

If the hash was not found, then the hash of the n-2 previous words is calculated and looked up in the combined transition probabilities table. Essentially this means looking up the transition probability for the n-2-grams.

+

If the hash was not found, then the hash of the n-2 previous words is calculated and looked up in the combined transition probabilities table.

This process is repeated until there are no previous words. When this happens, the model returns a “word not found” message.

This method of checking the transition probabilities of lower level n-grams is called back-off. An alternate method of predicting a word is to use interpolation. This involves weighing and summing the probabilities for each n-gram size.

@@ -129,12 +130,13 @@

Predicting the model performance

The wordpredictor package provides methods for performing intrinsic and extrinsic evaluation of the n-gram model.

-

Intrinsic evaluation involves calculating the mean Perplexity score for all sentences in a validation text file. The Perplexity for a sentence is calculated by taking the N-th root of the inverse of the product of probabilities of all words in a sentence.

-

N is the number of words in the sentence. The probability of a word is calculated by considering all n-1 words before that word. If the word was not found in the transition probabilities table, then the n-2 words are looked up. This process is repeated until there are no previous words.

+

The wordpredictor package performs intrinsic evaluation by calculating the mean Perplexity score for all sentences in a validation text file. The Perplexity for a sentence is calculated by taking the N-th root of the inverse of the product of probabilities of all words in a sentence. N is the number of words in the sentence.

+

The probability of a word is calculated by considering all n-1 words before that word. If the word was not found in the transition probabilities table, then the n-2 words are looked up. This process is repeated until there are no previous words.

If the word was found in the 1-gram list, then the probability of the word is calculated by simply dividing the number of times the word occurs by the total number words.

-

If the word was not found in the 1-gram list, then the model uses a default probability as the probability of the word. The default probability is calculated using Laplace Smoothing. Laplace Smoothing involves adding 1 to the frequency count of each word in the vocabulary.

-

Essentially this means that the total number of words in the data set are increased by vc, where vc is the number of words in the vocabulary. The wordpredictor package uses the file /usr/share/dict/cracklib-small as the dictionary file. This file is pre-installed in most Linux distributions.

-

In Laplace Smoothing 1 is added to the word count. Since an unknown word occurs zero times, after Laplace Smoothing it will have a count of 1. So the default probability is calculated as: P(unk) = 1/(N+VC), where N is the total number of words in the data set and VC is the number of words in the vocabulary. This default probability is assigned to unknown words. Alternative methods to Laplace Smoothing are Add-k smoothing, Kneser-Ney smoothing and Good-Turing Smoothing.

+

If the word was not found in the 1-gram list, then the model uses a default probability as the probability of the word. The default probability is calculated using Laplace Smoothing.

+

Laplace Smoothing involves adding 1 to the frequency count of each word in the vocabulary. Essentially this means that the total number of words in the data set are increased by vc, where vc is the number of words in the vocabulary.

+

In Laplace Smoothing 1 is added to the word count. Since an unknown word occurs zero times, after Laplace Smoothing it will have a count of 1. So the default probability is calculated as: P(unk) = 1/(N+VC), where N is the total number of words in the data set and VC is the number of words in the vocabulary. This default probability is assigned to unknown words. Alternative methods to Laplace Smoothing are Add-k smoothing, Kneser-Ney smoothing and Good-Turing Smoothing.

+

The wordpredictor package uses the file /usr/share/dict/cracklib-small as the dictionary file. This file is pre-installed in most Linux distributions.

Extrinsic evaluation involves calculating the accuracy score. The model tries to predict the last word of a sentence. If the actual last word was one of the 3 words predicted by the model, then the prediction is considered to be accurate. The accuracy score is the number of sentences that were correctly predicted.

@@ -142,7 +144,12 @@

Generating the model

The ModelGenerator class allows generating the final n-gram model using a single method call. The following example generates a n-gram model using default data cleaning and tokenization options:

-# The following code generates n-gram model using default options for data
+# The required files
+rf <- c("input.txt")
+# The test environment is setup
+ed <- setup_env(rf, ve)
+
+# The following code generates n-gram model using default options for data
 # cleaning and tokenization. See the following section on how to customize these
 # options. Note that input.txt is the name of the input data file. It should be
 # present in the data directory. ddir is the data directory. mdir is the model
@@ -151,70 +158,136 @@ 

# ModelGenerator class object is created mg <- ModelGenerator$new( - name = "def-model", - desc = "N-gram model generating using default options", - fn = "def-model.RDS", - df = "input.txt", - n = 4, - ssize = 10, - ddir = ddir1, - mdir = mdir, - dc_opts = list(), - tg_opts = list(), - ve = 2 + name = "def-model", + desc = "N-gram model generating using default options", + fn = "def-model.RDS", + df = "input.txt", + n = 4, + ssize = 0.1, + dir = ed, + dc_opts = list(), + tg_opts = list(), + ve = ve ) # Generates n-gram model. The output is the file # ./data/model/def-model.RDS -mg$generate_model()

+mg$generate_model() + +# The test envionment is cleaned up +clean_up(ve)

Evaluating the model performance

The wordpredictor package provides the ModelEvaluator class for evaluating the performance of the generated n-gram model.

-

The following example performs Intrinsic evaluation. It measures the Perplexity score for each sentence in the validation.txt file. It returns the minimum, mean and maximum Perplexity score for each line.

+

The following example performs intrinsic evaluation. It measures the Perplexity score for each sentence in the validation.txt file. It returns the minimum, mean and maximum Perplexity score for each line.

-# The model file name
-mfn <- paste0(mdir, "/def-model.RDS")
+# The required files
+rf <- c("def-model.RDS", "validate-clean.txt")
+# The test environment is setup
+ed <- setup_env(rf, ve)
+
+# The model file name
+mfn <- paste0(ed, "/def-model.RDS")
 # The path to the cleaned validation file
-vfn <- paste0(mdir, "/validate.txt")
+vfn <- paste0(ed, "/validate-clean.txt")
 # ModelEvaluator class object is created
-me <- ModelEvaluator$new(mf = mfn)
+me <- ModelEvaluator$new(mf = mfn, ve = ve)
 # The intrinsic evaluation is performed on first 20 lines
-stats <- me$intrinsic_evaluation(lc = 20, fn = vfn)
-

The following example performs Extrinsic evaluation. It measures the accuracy score for each sentence in validation.txt file. For each sentence the model is used to predict the last word in the sentence given the previous words. If the last word was correctly predicted, then the prediction is considered to be accurate. The Extrinsic evaluation returns the number of correct and incorrect predictions.

+stats <- me$intrinsic_evaluation(lc = 20, fn = vfn) + +# The test envionment is cleaned up +clean_up(ve)
+

The following example performs extrinsic evaluation. It measures the accuracy score for each sentence in validation.txt file. For each sentence the model is used to predict the last word in the sentence given the previous words. If the last word was correctly predicted, then the prediction is considered to be accurate. The extrinsic evaluation returns the number of correct and incorrect predictions.

-# The model file name
-mfn <- paste0(mdir, "/def-model.RDS")
+# The required files
+rf <- c("def-model.RDS", "validate-clean.txt")
+# The test environment is setup
+ed <- setup_env(rf, ve)
+
+# The model file name
+mfn <- paste0(ed, "/def-model.RDS")
 # The path to the cleaned validation file
-vfn <- paste0(mdir, "/validate.txt")
+vfn <- paste0(ed, "/validate-clean.txt")
 # ModelEvaluator class object is created
-me <- ModelEvaluator$new(mf = mfn)
+me <- ModelEvaluator$new(mf = mfn, ve = ve)
 # The intrinsic evaluation is performed on first 100 lines
-stats <- me$extrinsic_evaluation(lc = 100, fn = vfn)
+stats <- me$extrinsic_evaluation(lc = 100, fn = vfn) + +# The test envionment is cleaned up +clean_up(ve)

How to predict a word

The following example shows how to predict the next word. It returns the 3 possible next words along with their probabilities.

-# The model file name
-mfn <- paste0(mdir, "/def-model.RDS")
+# The required files
+rf <- c("def-model.RDS", "validate-clean.txt")
+# The test environment is setup
+ed <- setup_env(rf, ve)
+
+# The model file name
+mfn <- paste0(ed, "/def-model.RDS")
 # An object of class ModelPredictor is created. The mf parameter is the name of
 # the model file that was generated in the previous example.
-mp <- ModelPredictor$new(mf = mfn)
+mp <- ModelPredictor$new(mf = mfn, ve = ve)
 # Given the words: "how are", the next word is predicted. The top 3 most likely
 # next words are returned along with their respective probabilities.
 res <- mp$predict_word(words = "how are", 3)
-print(res)
-#> $found
-#> [1] TRUE
-#> 
-#> $words
-#> [1] "you"        "indigenous" "new"       
-#> 
-#> $probs
-#> [1] 0.5714286 0.1428571 0.1428571
+# The test envionment is cleaned up +clean_up(ve)
+ +
+

+Package dependencies

+

The wordpredictor package uses the following packages: digest, dply, ggplot2, pryr, R6, testthat and stingr

+

The following packages were useful during package development: quanteda, tm and hash lintr styler pkgdown

+
+ +
+

+Bibliography

+
+
+

Feinerer, Ingo, Kurt Hornik, and David Meyer. 2008. “Text Mining Infrastructure in R.” Journal of Statistical Software, Articles 25 (5): 1–54. https://doi.org/10.18637/jss.v025.i05.

+
+
+

Hadley Wickham, Gábor Csárdi, Peter Danenberg. 2020. In-Line Documentation for R. https://cran.r-project.org/web/packages/roxygen2/vignettes/rd.html.

+
+
+

Jurafsky, Dan, and James H. Martin. 2020. “N-Gram Language Models.” In Speech and Language Processing, third. https://web.stanford.edu/~jurafsky/slp3/3.pdf.

+
+
+

McKeown, Kathy. 2009. “N-Grams and Corpus Linguistics.” In. http://www1.cs.columbia.edu/~kathy/NLP/ClassSlides/Class3-ngrams09/ngrams.pdf.

+
+
+

Wickham, Hadley. 2021. Advanced R. Second. https://adv-r.hadley.nz/index.html.

+
+
+

Wickham, Hadley, and Jenny Bryan. 2021. R Packages. Second. https://r-pkgs.org/index.html.

+
+
+

Wikipedia contributors. 2021a. “N-Gram — Wikipedia, the Free Encyclopedia.” https://en.wikipedia.org/w/index.php?title=N-gram&oldid=1018953040.

+
+
+

———. 2021b. “Perplexity — Wikipedia, the Free Encyclopedia.” https://en.wikipedia.org/w/index.php?title=Perplexity&oldid=1022965742.

+
+
+

Yihui Xie, Emily Riederer, Christophe Dervieux. 2021. R Markdown Cookbook. First. https://bookdown.org/yihui/rmarkdown-cookbook/.

+
+
diff --git a/docs/authors.html b/docs/authors.html index 1301d76..137bf64 100644 --- a/docs/authors.html +++ b/docs/authors.html @@ -6,7 +6,7 @@ -Authors • Word Predictor +Citation and Authors • Word Predictor @@ -39,7 +39,7 @@ - + @@ -58,7 +58,7 @@ -
+
@@ -129,23 +130,24 @@
-

It provides a method for generating training, testing and -validation data sets from a given input text file. It also provides a method -for generating a sample file of given size or number of lines from an input -text file.

+

It provides a method for generating training, testing and validation data +sets from a given input text file.

+

It also provides a method for generating a sample file of given size or +number of lines from an input text file. The contents of the sample file may +be cleaned or randomized.

Super class

-

wordpredictor::TextFileProcessor -> DataSampler

+

wordpredictor::Base -> DataSampler

Methods

@@ -166,28 +168,27 @@

Pub

Method new()

It initializes the current object. It is used to set the verbose option.

Usage

-

DataSampler$new(ddir = "./data", mdir = "./models", ve = 0)

+

DataSampler$new(dir = ".", ve = 0)

Arguments

-
ddir

The data directory.

+
dir

The directory for storing the input and output files.

-
mdir

The model directory.

- -
ve

If progress information should be displayed.

+
ve

The level of detail in the information messages.


Method generate_sample()

Generates a sample file of given size from the given input file. The -file is optionally cleaned and saved to the data directory.

Usage

-

DataSampler$generate_sample(fn, ss, ic, ir, ofn, is)

+file is saved to the directory given by the dir object attribute. +Once the file has been generated, its contents may be cleaned or +randomized.

Usage

+

DataSampler$generate_sample(fn, ss, ic, ir, ofn, is, dc_opts = NULL)

Arguments

fn

The input file name. It is the short file name relative to -the ddir. If not given, then the file name is auto generated from -the type parameter.

+the dir attribute.

ss

The number of lines or proportion of lines to sample.

@@ -195,30 +196,102 @@

Arg
ir

If the sample file contents should be randomized.

-
ofn

The output file name. It will be saved to the ddir.

+
ofn

The output file name. It will be saved to the dir.

is

If the sampled data should be saved to a file.

+
dc_opts

The options for cleaning the data.

+

+

Examples

+

# Start of environment setup code
+# The level of detail in the information messages
+ve <- 0
+# The name of the folder that will contain all the files. It will be
+# created in the current directory. NULL implies tempdir will be used
+fn <- NULL
+# The required files. They are default files that are part of the
+# package
+rf <- c("input.txt")
+# An object of class EnvManager is created
+em <- EnvManager$new(ve = ve, rp = "./")
+# The required files are downloaded
+ed <- em$setup_env(rf, fn)
+# End of environment setup code
+
+# The sample file name
+sfn <- paste0(ed, "/sample.txt")
+# An object of class DataSampler is created
+ds <- DataSampler$new(dir = ed, ve = ve)
+# The sample file is generated
+ds$generate_sample(
+    fn = "input.txt",
+    ss = 0.5,
+    ic = FALSE,
+    ir = FALSE,
+    ofn = "sample.txt",
+    is = TRUE
+)
+
+# The test environment is removed. Comment the below line, so the
+# files generated by the function can be viewed
+em$td_env()
+

+


Method generate_data()

It generates training, testing and validation data sets from the given input file. It first reads the file given as a parameter to the current object. It partitions the data into -training, testing and validation sets, according to the given -parameters. The files are named train.txt, test.txt and va.txt. The -files are saved to the given output folder.

Usage

-

DataSampler$generate_data(fn, dir, percs)

+training, testing and validation sets, according to the perc +parameter. The files are named train.txt, test.txt and va.txt and are +saved to the given output folder.

Usage

+

DataSampler$generate_data(fn, percs)

Arguments

-
fn

The input file name. It should be relative to the ddir.

- -
dir

The name of the output folder.

+
fn

The input file name. It should be relative to the dir +attribute.

percs

The size of the training, testing and validation sets.

+

Examples

+

# Start of environment setup code
+# The level of detail in the information messages
+ve <- 0
+# The name of the folder that will contain all the files. It will be
+# created in the current directory. NULL implies tempdir will be
+# used
+fn <- NULL
+# The required files. They are default files that are part of the
+# package
+rf <- c("input.txt")
+# An object of class EnvManager is created
+em <- EnvManager$new(ve = ve)
+# The required files are downloaded
+ed <- em$setup_env(rf, fn)
+# End of environment setup code
+
+# The files to clean
+fns <- c("train", "test", "validate")
+# An object of class DataSampler is created
+ds <- DataSampler$new(dir = ed, ve = ve)
+# The train, test and validation files are generated
+ds$generate_data(
+    fn = "input.txt",
+    percs = list(
+        "train" = 0.8,
+        "test" = 0.1,
+        "validate" = 0.1
+    )
+)
+
+# The test environment is removed. Comment the below line, so the
+# files generated by the function can be viewed
+em$td_env()
+

+


Method clone()

The objects of this class are cloneable with this method.

Usage

@@ -232,6 +305,83 @@

Arg +

Examples

+
+## ------------------------------------------------ +## Method `DataSampler$generate_sample` +## ------------------------------------------------ + +# Start of environment setup code +# The level of detail in the information messages +ve <- 0 +# The name of the folder that will contain all the files. It will be +# created in the current directory. NULL implies tempdir will be used +fn <- NULL +# The required files. They are default files that are part of the +# package +rf <- c("input.txt") +# An object of class EnvManager is created +em <- EnvManager$new(ve = ve, rp = "./") +# The required files are downloaded +ed <- em$setup_env(rf, fn) +# End of environment setup code + +# The sample file name +sfn <- paste0(ed, "/sample.txt") +# An object of class DataSampler is created +ds <- DataSampler$new(dir = ed, ve = ve) +# The sample file is generated +ds$generate_sample( + fn = "input.txt", + ss = 0.5, + ic = FALSE, + ir = FALSE, + ofn = "sample.txt", + is = TRUE +) + +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed +em$td_env() + +## ------------------------------------------------ +## Method `DataSampler$generate_data` +## ------------------------------------------------ + +# Start of environment setup code +# The level of detail in the information messages +ve <- 0 +# The name of the folder that will contain all the files. It will be +# created in the current directory. NULL implies tempdir will be +# used +fn <- NULL +# The required files. They are default files that are part of the +# package +rf <- c("input.txt") +# An object of class EnvManager is created +em <- EnvManager$new(ve = ve) +# The required files are downloaded +ed <- em$setup_env(rf, fn) +# End of environment setup code + +# The files to clean +fns <- c("train", "test", "validate") +# An object of class DataSampler is created +ds <- DataSampler$new(dir = ed, ve = ve) +# The train, test and validation files are generated +ds$generate_data( + fn = "input.txt", + percs = list( + "train" = 0.8, + "test" = 0.1, + "validate" = 0.1 + ) +) + +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed +em$td_env() +
@@ -129,34 +129,34 @@
-

The attributes of this class are used to store n-gram model -information. The class provides methods for loading and saving the model.

+

The Model class represents n-gram models. An instance of the class is a +single n-gram model. The attributes of this class are used to store n-gram +model information. The class provides methods for loading and saving the +model.

Details

-

The attributes of this class are used to store n-gram model -information such as model name, model description, model file name, n-gram -number, transition probabilities data, default probability, n-gram -configuration options such as data cleaning and tokenization options, word -list, model path, data directory path and performance stats. The model is -saved to a single file as a R object. A model file contains all the -information required by the model. The model object is used by other model -classes that perform operations on the model such as evaluation of model -performance, making word predictions based on the model and plotting model -performance stats.

+

The attributes of this class are used to store n-gram model information such +as model name, model description, model file name, n-gram size, transition +probabilities data, default probability for words, data cleaning and +tokenization options, word list, model path, data directory path and +performance stats. The model is saved to a single file as a R object.

+

A model file contains all the information required by the model. The model +object is used as input by classes that perform operations on the model such +as evaluation of model performance, text predictions and comparision of model +performance.

Super class

-

wordpredictor::TextFileProcessor -> Model

+

wordpredictor::Base -> Model

Public fields

@@ -176,6 +176,7 @@

Pub
  • Model$new()

  • Model$load_model()

  • Model$get_config()

  • +
  • Model$get_size()

  • Model$clone()

  • Inherited methods @@ -186,7 +187,7 @@

    Pub

    Method new()

    It initializes the current object. It is used to set the -maximum ngram number, sample size, input file name, data cleaner +maximum n-gram number, sample size, input file name, data cleaner options, tokenization options, combined transition probabilities file name and verbose.

    Usage

    Model$new(
    @@ -195,9 +196,8 @@ 

    Pub fn = NULL, df = NULL, n = 4, - ssize = 30, - ddir = "./data", - mdir = "./models", + ssize = 0.3, + dir = ".", dc_opts = list(), tg_opts = list(), ve = 0 @@ -211,23 +211,19 @@

    Arg
    fn

    The model file name.

    -
    df

    The path of the file used to generate the model. If -the data was cleaned, then df is the path to the cleaned -file.

    +
    df

    The name of the file used to generate the model.

    -
    n

    The maximum ngram number supported by the model.

    +
    n

    The maximum n-gram number supported by the model.

    -
    ssize

    The sample size in Mb.

    +
    ssize

    The sample size as a proportion of the input file.

    -
    ddir

    The data directory.

    - -
    mdir

    The model directory.

    +
    dir

    The directory containing the model files.

    dc_opts

    The data cleaner options.

    tg_opts

    The token generator options.

    -
    ve

    If progress information should be displayed.

    +
    ve

    The level of detail in the information messages.


    @@ -248,6 +244,14 @@

    Arg

    Returns

    The configuration value.


    +

    Method get_size()

    +

    It returns the size of the current object. The object +size is calculated as the sum of sizes of the object attributes.

    Usage

    +

    Model$get_size()

    + +

    Returns

    +

    The size of the object in bytes.

    +


    Method clone()

    The objects of this class are cloneable with this method.

    Usage

    Model$clone(deep = FALSE)

    diff --git a/docs/reference/ModelEvaluator-1.png b/docs/reference/ModelEvaluator-1.png new file mode 100644 index 0000000000000000000000000000000000000000..e7b6c05fe251da91a6e537cf60b073d63f75049f GIT binary patch literal 82987 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⪼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 z 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 literal 0 HcmV?d00001 diff --git a/docs/reference/ModelEvaluator.html b/docs/reference/ModelEvaluator.html index a9b0b11..a678c29 100644 --- a/docs/reference/ModelEvaluator.html +++ b/docs/reference/ModelEvaluator.html @@ -6,7 +6,7 @@ -It performs extrinsic and intrinsic evaluation of a n-gram model — ModelEvaluator • Word Predictor +Evaluates performance of n-gram models — ModelEvaluator • Word Predictor @@ -39,10 +39,12 @@ - + @@ -75,7 +77,7 @@ Word Predictor - 1.0.0 + 0.0.1
    @@ -129,15 +131,17 @@

    It provides methods for performing extrinsic and intrinsic -evaluation. Intrinsic evaluation is based on calculation of Perplexity. -Extrinsic evaluation involves determining the percentage of correct next word +evaluation of a n-gram model. It also provides a method for comparing +performance of multiple n-gram models.

    +

    Intrinsic evaluation is based on calculation of Perplexity. Extrinsic +evaluation involves determining the percentage of correct next word predictions.

    @@ -145,18 +149,18 @@

    It performs extrinsic and intrinsic evaluation of a n-gram model

    Details

    -

    Before performing the intrinsic and extrinsic model evaluation, a -validation file must be first generated. This can be done using the Generator -class. Each line in the validation file is evaluated. For intrinsic -evaluation Perplexity for the line is calculated. An overall summary of the -Perplexity calculations is returned. It includes the min, max and mean -Perplexity. For extrinsic evaluation, next word prediction is performed on -each line. If the actual next word is one of the three predicted next words, -then the prediction is considered to be accurate. The extrinsic evaluation -returns the percentage of correct and incorrect predictions.

    +

    Before performing the intrinsic and extrinsic model evaluation, a validation +file must be first generated. This can be done using the DataSampler class.

    +

    Each line in the validation file is evaluated. For intrinsic evaluation +Perplexity for the line is calculated. An overall summary of the Perplexity +calculations is returned. It includes the min, max and mean Perplexity.

    +

    For extrinsic evaluation, next word prediction is performed on each line. If +the actual next word is one of the three predicted next words, then the +prediction is considered to be accurate. The extrinsic evaluation returns the +percentage of correct and incorrect predictions.

    Super class

    -

    wordpredictor::TextFileProcessor -> ModelEvaluator

    +

    wordpredictor::Base -> ModelEvaluator

    Methods

    @@ -186,17 +190,18 @@

    Arg

    mf

    The model file name.

    -
    ve

    If progress information should be displayed.

    +
    ve

    The level of detail in the information messages.


    Method compare_performance()

    -

    It compares the performance of the models in the given -folder. The performance of the model is compared for the 4 metric -which are time taken, memory used, Perplexity and accuracy. The -performance comparision is displayed on plots. 4 plots are displayed. -One for each performance metric. A fifth plot shows the variation of -Perplexity with accuracy. All 5 plots are plotted on one page.

    Usage

    +

    It compares the performance of the models in the given folder.

    +

    The performance of the model is compared for the 4 metric which are +time taken, memory used, Perplexity and accuracy. The performance +comparison is displayed on plots.

    +

    4 plots are displayed. One for each performance metric. A fifth plot +shows the variation of Perplexity with accuracy. All 5 plots are +plotted on one page.

    Usage

    ModelEvaluator$compare_performance(opts)

    Arguments

    @@ -205,17 +210,46 @@

    Arg
    • save_to. The graphics device to save the plot to. NULL implies plot is printed.

    • -
    • dir. The directory where the plot and stats will be saved.

    • -
    • mdir. The directory containing the model files.

    • +
    • dir. The directory containing the model file, plot and stats.

    +

    Examples

    +

    # Start of environment setup code
    +# The level of detail in the information messages
    +ve <- 0
    +# The name of the folder that will contain all the files. It will be
    +# created in the current directory. NULL implies tempdir will be
    +# used.
    +fn <- NULL
    +# The required files. They are default files that are part of the
    +# package
    +rf <- c("def-model.RDS")
    +# An object of class EnvManager is created
    +em <- EnvManager$new(ve = ve, rp = "./")
    +# The required files are downloaded
    +ed <- em$setup_env(rf, fn)
    +# End of environment setup code
    +# ModelEvaluator class object is created
    +me <- ModelEvaluator$new(ve = ve)
    +# The performance evaluation is performed
    +me$compare_performance(opts = list(
    +    "save_to" = NULL,
    +    "dir" = ed
    +))
    +
    +# The test environment is removed. Comment the below line, so the
    +# files generated by the function can be viewed
    +em$td_env()
    +

    +


    Method plot_stats()

    -

    It plots the given stats on 5 plots. The plots are -displayed on a single page. The 4 performance metrics which are time -taken, memory, Perplexity and accuracy are plotted against the model -name. Another plot compares Perplexity with accuracy for each model.

    Usage

    +

    It plots the given stats on 5 plots. The plots are displayed on a +single page.

    +

    The 4 performance metrics which are time taken, memory, Perplexity +and accuracy are plotted against the model name. Another plot +compares Perplexity with accuracy for each model.

    Usage

    ModelEvaluator$plot_stats(data)

    Arguments

    @@ -227,10 +261,18 @@

    Ret

    The ggplot object is returned.


    Method evaluate_performance()

    -

    It performs intrinsic and extrinsic evaluation for the -given model. It also measures the memory usage and time taken. The -performance metrics are displayed in 5 plots on one page. Performance -statistics are saved to the model object.

    Usage

    +

    It performs intrinsic and extrinsic evaluation for the given model +and validation text file. The given number of lines in the validation +file are used in the evaluation

    +

    It performs two types of evaluations. One is intrinsic evaluation, +based on Perplexity, the other is extrinsic evaluation based on +accuracy.

    +

    It returns the results of evaluation. 4 evaluation metrics are +returned. Perplexity, accuracy, memory and time taken. Memory is the +size of the model object. Time taken is the time needed for +performing both evaluations.

    +

    The results of the model evaluation are saved within the model object +and also returned.

    Usage

    ModelEvaluator$evaluate_performance(lc, fn)

    Arguments

    @@ -245,6 +287,39 @@

    Arg

    Returns

    The performance stats are returned.

    +

    Examples

    +

    # Start of environment setup code
    +# The level of detail in the information messages
    +ve <- 0
    +# The name of the folder that will contain all the files. It will be
    +# created in the current directory. NULL implies tempdir will be used
    +fn <- NULL
    +# The required files. They are default files that are part of the
    +# package
    +rf <- c("def-model.RDS", "validate-clean.txt")
    +# An object of class EnvManager is created
    +em <- EnvManager$new(ve = ve, rp = "./")
    +# The required files are downloaded
    +ed <- em$setup_env(rf, fn)
    +# End of environment setup code
    +
    +# The model file name
    +mfn <- paste0(ed, "/def-model.RDS")
    +# The validation file name
    +vfn <- paste0(ed, "/validate-clean.txt")
    +
    +# ModelEvaluator class object is created
    +me <- ModelEvaluator$new(mf = mfn, ve = ve)
    +# The performance evaluation is performed
    +stats <- me$evaluate_performance(lc = 20, fn = vfn)
    +# The evaluation stats are printed
    +print(stats)
    +
    +# The test environment is removed. Comment the below line, so the
    +# files generated by the function can be viewed
    +em$td_env()
    +

    +


    Method intrinsic_evaluation()

    Evaluates the model using intrinsic evaluation based on @@ -264,11 +339,45 @@

    Arg

    Returns

    The min, max and mean Perplexity score.

    +

    Examples

    +

    # Start of environment setup code
    +# The level of detail in the information messages
    +ve <- 0
    +# The name of the folder that will contain all the files. It will be
    +# created in the current directory. NULL implies tempdir will be used
    +fn <- NULL
    +# The required files. They are default files that are part of the
    +# package
    +rf <- c("def-model.RDS", "validate-clean.txt")
    +# An object of class EnvManager is created
    +em <- EnvManager$new(ve = ve, rp = "./")
    +# The required files are downloaded
    +ed <- em$setup_env(rf, fn)
    +# End of environment setup code
    +
    +# The model file name
    +mfn <- paste0(ed, "/def-model.RDS")
    +# The validation file name
    +vfn <- paste0(ed, "/validate-clean.txt")
    +
    +# ModelEvaluator class object is created
    +me <- ModelEvaluator$new(mf = mfn, ve = ve)
    +# The intrinsic evaluation is performed
    +stats <- me$intrinsic_evaluation(lc = 20, fn = vfn)
    +# The evaluation stats are printed
    +print(stats)
    +
    +# The test environment is removed. Comment the below line, so the
    +# files generated by the function can be viewed
    +em$td_env()
    +

    +


    Method extrinsic_evaluation()

    Evaluates the model using extrinsic evaluation based on Accuracy. The given number of sentences are taken from the validation -file. For each sentence, the model is used to predict the next word. +file.

    +

    For each sentence, the model is used to predict the next word. The accuracy stats are returned. A prediction is considered to be correct if one of the predicted words matches the actual word.

    Usage

    ModelEvaluator$extrinsic_evaluation(lc, fn)

    @@ -283,6 +392,39 @@

    Arg

    Returns

    The number of correct and incorrect predictions.

    +

    Examples

    +

    # Start of environment setup code
    +# The level of detail in the information messages
    +ve <- 0
    +# The name of the folder that will contain all the files. It will be
    +# created in the current directory. NULL implies tempdir will be used
    +fn <- NULL
    +# The required files. They are default files that are part of the
    +# package
    +rf <- c("def-model.RDS", "validate-clean.txt")
    +# An object of class EnvManager is created
    +em <- EnvManager$new(ve = ve, rp = "./")
    +# The required files are downloaded
    +ed <- em$setup_env(rf, fn)
    +# End of environment setup code
    +
    +# The model file name
    +mfn <- paste0(ed, "/def-model.RDS")
    +# The validation file name
    +vfn <- paste0(ed, "/validate-clean.txt")
    +
    +# ModelEvaluator class object is created
    +me <- ModelEvaluator$new(mf = mfn, ve = ve)
    +# The intrinsic evaluation is performed
    +stats <- me$extrinsic_evaluation(lc = 100, fn = vfn)
    +# The evaluation stats are printed
    +print(stats)
    +
    +# The test environment is removed. Comment the below line, so the
    +# files generated by the function can be viewed
    +em$td_env()
    +

    +


    Method clone()

    The objects of this class are cloneable with this method.

    Usage

    @@ -296,6 +438,174 @@

    Arg +

    Examples

    +
    +## ------------------------------------------------ +## Method `ModelEvaluator$compare_performance` +## ------------------------------------------------ + +# Start of environment setup code +# The level of detail in the information messages +ve <- 0 +# The name of the folder that will contain all the files. It will be +# created in the current directory. NULL implies tempdir will be +# used. +fn <- NULL +# The required files. They are default files that are part of the +# package +rf <- c("def-model.RDS") +# An object of class EnvManager is created +em <- EnvManager$new(ve = ve, rp = "./") +# The required files are downloaded +ed <- em$setup_env(rf, fn) +# End of environment setup code +# ModelEvaluator class object is created +me <- ModelEvaluator$new(ve = ve) +# The performance evaluation is performed +me$compare_performance(opts = list( + "save_to" = NULL, + "dir" = ed +)) +
    +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed +em$td_env() + +## ------------------------------------------------ +## Method `ModelEvaluator$evaluate_performance` +## ------------------------------------------------ + +# Start of environment setup code +# The level of detail in the information messages +ve <- 0 +# The name of the folder that will contain all the files. It will be +# created in the current directory. NULL implies tempdir will be used +fn <- NULL +# The required files. They are default files that are part of the +# package +rf <- c("def-model.RDS", "validate-clean.txt") +# An object of class EnvManager is created +em <- EnvManager$new(ve = ve, rp = "./") +# The required files are downloaded +ed <- em$setup_env(rf, fn) +# End of environment setup code + +# The model file name +mfn <- paste0(ed, "/def-model.RDS") +# The validation file name +vfn <- paste0(ed, "/validate-clean.txt") + +# ModelEvaluator class object is created +me <- ModelEvaluator$new(mf = mfn, ve = ve) +# The performance evaluation is performed +stats <- me$evaluate_performance(lc = 20, fn = vfn) +# The evaluation stats are printed +print(stats) +
    #> $m +#> [1] 279320 +#> +#> $t +#> [1] 0.747 +#> +#> $p +#> [1] 2297.35 +#> +#> $a +#> [1] 0 +#>
    +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed +em$td_env() + +## ------------------------------------------------ +## Method `ModelEvaluator$intrinsic_evaluation` +## ------------------------------------------------ + +# Start of environment setup code +# The level of detail in the information messages +ve <- 0 +# The name of the folder that will contain all the files. It will be +# created in the current directory. NULL implies tempdir will be used +fn <- NULL +# The required files. They are default files that are part of the +# package +rf <- c("def-model.RDS", "validate-clean.txt") +# An object of class EnvManager is created +em <- EnvManager$new(ve = ve, rp = "./") +# The required files are downloaded +ed <- em$setup_env(rf, fn) +# End of environment setup code + +# The model file name +mfn <- paste0(ed, "/def-model.RDS") +# The validation file name +vfn <- paste0(ed, "/validate-clean.txt") + +# ModelEvaluator class object is created +me <- ModelEvaluator$new(mf = mfn, ve = ve) +# The intrinsic evaluation is performed +stats <- me$intrinsic_evaluation(lc = 20, fn = vfn) +# The evaluation stats are printed +print(stats) +
    #> $min +#> [1] 282 +#> +#> $max +#> [1] 8248 +#> +#> $mean +#> [1] 2297.35 +#>
    +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed +em$td_env() + +## ------------------------------------------------ +## Method `ModelEvaluator$extrinsic_evaluation` +## ------------------------------------------------ + +# Start of environment setup code +# The level of detail in the information messages +ve <- 0 +# The name of the folder that will contain all the files. It will be +# created in the current directory. NULL implies tempdir will be used +fn <- NULL +# The required files. They are default files that are part of the +# package +rf <- c("def-model.RDS", "validate-clean.txt") +# An object of class EnvManager is created +em <- EnvManager$new(ve = ve, rp = "./") +# The required files are downloaded +ed <- em$setup_env(rf, fn) +# End of environment setup code + +# The model file name +mfn <- paste0(ed, "/def-model.RDS") +# The validation file name +vfn <- paste0(ed, "/validate-clean.txt") + +# ModelEvaluator class object is created +me <- ModelEvaluator$new(mf = mfn, ve = ve) +# The intrinsic evaluation is performed +stats <- me$extrinsic_evaluation(lc = 100, fn = vfn) +# The evaluation stats are printed +print(stats) +
    #> $valid +#> [1] 1 +#> +#> $invalid +#> [1] 74 +#> +#> $valid_perc +#> [1] 1.333333 +#> +#> $invalid_perc +#> [1] 98.66667 +#>
    +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed +em$td_env() +
    @@ -128,15 +127,14 @@
    -

    It provides methods that are used for generating n-gram models. -The n-gram models may be customized by specifying the data cleaning and -tokenization options.

    +

    It provides a method for generating n-gram models. The n-gram models may be +customized by specifying data cleaning and tokenization options.

    @@ -144,13 +142,13 @@

    It is used to generate n-gram models for the given data file.

    Details

    It provides a method that generates a n-gram model. The n-gram model -may be customized by specifying the data cleaning and tokenization options. -The data cleaning options include removal of punctuation, stop words, extra +may be customized by specifying the data cleaning and tokenization options.

    +

    The data cleaning options include removal of punctuation, stop words, extra space, non-dictionary words and bad words. The tokenization options include n-gram number and word stemming.

    Super class

    -

    wordpredictor::TextFileProcessor -> ModelGenerator

    +

    wordpredictor::Base -> ModelGenerator

    Methods

    @@ -168,19 +166,17 @@

    Pub

    Method new()

    -

    It initializes the current object. It is used to set the -maximum ngram number, sample size, input file name, data cleaner -options, tokenization options, combined transition probabilities file -name and verbose.

    Usage

    +

    It initializes the current object. It is used to set the maximum +n-gram number, sample size, input file name, data cleaner options, +tokenization options and verbose option.

    Usage

    ModelGenerator$new(
       name = NULL,
       desc = NULL,
       fn = NULL,
       df = NULL,
       n = 4,
    -  ssize = 30,
    -  ddir = "./data",
    -  mdir = "./models",
    +  ssize = 0.3,
    +  dir = ".",
       dc_opts = list(),
       tg_opts = list(),
       ve = 0
    @@ -192,38 +188,68 @@ 

    Arg
    desc

    The model description.

    -
    fn

    The model file name. If not set, then model.RDS is used.

    +
    fn

    The model file name.

    -
    df

    The path of the file used to generate the model. If the -data was cleaned, then df is the path to the cleaned file. It -should be the short file name. It should be present in the data -directory.

    +
    df

    The path of the input text file. It should be the short +file name and should be present in the data directory.

    -
    n

    The maximum ngram number supported by the model.

    +
    n

    The n-gram size of the model.

    -
    ssize

    The sample size in Mb.

    +
    ssize

    The sample size as a proportion of the input file.

    -
    ddir

    The data directory.

    - -
    mdir

    The model directory.

    +
    dir

    The directory containing the input and output files.

    dc_opts

    The data cleaner options.

    tg_opts

    The token generator options.

    -
    ve

    If progress information should be displayed.

    +
    ve

    The level of detail in the information messages.


    Method generate_model()

    -

    It generates the model using the current object's attributes.

    Usage

    -

    ModelGenerator$generate_model(rf = F)

    +

    It generates the model using the parameters passed to +the object's constructor. It generates a n-gram model file and saves +it to the model directory.

    Usage

    +

    ModelGenerator$generate_model()

    + +

    Examples

    +

    # Start of environment setup code
    +# The level of detail in the information messages
    +ve <- 0
    +# The name of the folder that will contain all the files. It will be
    +# created in the current directory. NULL implies tempdir will be used
    +fn <- NULL
    +# The required files. They are default files that are part of the
    +# package
    +rf <- c("input.txt")
    +# An object of class EnvManager is created
    +em <- EnvManager$new(ve = ve, rp = "./")
    +# The required files are downloaded
    +ed <- em$setup_env(rf, fn)
    +# End of environment setup code
    +
    +# ModelGenerator class object is created
    +mg <- ModelGenerator$new(
    +    name = "default-model",
    +    desc = "1 MB size and default options",
    +    fn = "def-model.RDS",
    +    df = "input.txt",
    +    n = 4,
    +    ssize = 0.99,
    +    dir = ed,
    +    dc_opts = list(),
    +    tg_opts = list(),
    +    ve = ve
    +)
    +# The n-gram model is generated
    +mg$generate_model()
    +
    +# The test environment is removed. Comment the below line, so the
    +# files generated by the function can be viewed
    +em$td_env()
    +

    -

    Arguments

    -

    -
    rf

    If the existing model files should be removed.

    - -


    Method clone()

    The objects of this class are cloneable with this method.

    Usage

    @@ -237,6 +263,47 @@

    Arg +

    Examples

    +
    +## ------------------------------------------------ +## Method `ModelGenerator$generate_model` +## ------------------------------------------------ + +# Start of environment setup code +# The level of detail in the information messages +ve <- 0 +# The name of the folder that will contain all the files. It will be +# created in the current directory. NULL implies tempdir will be used +fn <- NULL +# The required files. They are default files that are part of the +# package +rf <- c("input.txt") +# An object of class EnvManager is created +em <- EnvManager$new(ve = ve, rp = "./") +# The required files are downloaded +ed <- em$setup_env(rf, fn) +# End of environment setup code + +# ModelGenerator class object is created +mg <- ModelGenerator$new( + name = "default-model", + desc = "1 MB size and default options", + fn = "def-model.RDS", + df = "input.txt", + n = 4, + ssize = 0.99, + dir = ed, + dc_opts = list(), + tg_opts = list(), + ve = ve +) +# The n-gram model is generated +mg$generate_model() + +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed +em$td_env() +
    @@ -129,31 +129,23 @@
    -

    It provides methods that perform extrinsic and intrinsic model -evaluation. It also provides methods for determining the memory and time -requirements for generating the model. It also provides a method for -determining how much memory is used by the final model.

    +

    It provides a method for predicting the new word given a set of +previous words. It also provides a method for calculating the Perplexity +score for a set of words. Furthermore it provides a method for calculating +the probability of a given word and set of previous words.

    -

    Details

    - -

    It provides a method that performs intrinsic model evaluation based -on Perplexity. It also provides a method that performs extrinsic model -evalation based on accuracy. It provides a method for determining how much -memory and time is needed to generate a model for different input data sizes. -It provides a method for determining how much memory is needed by the final -model.

    Super class

    -

    wordpredictor::TextFileProcessor -> ModelPredictor

    +

    wordpredictor::Base -> ModelPredictor

    Methods

    @@ -182,7 +174,7 @@

    Arg

    mf

    The model file name.

    -
    ve

    If progress information should be displayed.

    +
    ve

    The level of detail in the information messages.


    @@ -199,7 +191,8 @@

    Ret calculated. The probabilities are multiplied and then inverted. The nth root of the result is the perplexity, where n is the number of words in the sentence. If the stem_words tokenization option was -specified, then the previous words are converted to their stem.

    Usage

    +specified when creating the given model file, then the previous words +are converted to their stems.

    Usage

    ModelPredictor$calc_perplexity(words)

    Arguments

    @@ -209,14 +202,48 @@

    Arg

    Returns

    The perplexity of the given list of words.

    +

    Examples

    +

    # Start of environment setup code
    +# The level of detail in the information messages
    +ve <- 0
    +# The name of the folder that will contain all the files. It will be
    +# created in the current directory. NULL implies tempdir will be used
    +fn <- NULL
    +# The required files. They are default files that are part of the
    +# package
    +rf <- c("def-model.RDS")
    +# An object of class EnvManager is created
    +em <- EnvManager$new(ve = ve, rp = "./")
    +# The required files are downloaded
    +ed <- em$setup_env(rf, fn)
    +# End of environment setup code
    +
    +# The model file name
    +mfn <- paste0(ed, "/def-model.RDS")
    +# ModelPredictor class object is created
    +mp <- ModelPredictor$new(mf = mfn, ve = ve)
    +# The sentence whoose Perplexity is to be calculated
    +l <- "last year at this time i was preparing for a trip to rome"
    +# The line is split in to words
    +w <- strsplit(l, " ")[[1]]
    +# The Perplexity of the sentence is calculated
    +p <- mp$calc_perplexity(w)
    +# The sentence Perplexity is printed
    +print(p)
    +# The test environment is removed. Comment the below line, so the
    +# files generated by the function can be viewed
    +em$td_env()
    +

    +


    Method predict_word()

    -

    Predicts the new word given a list of 1, 2 or 3 previous words. It -checks the given n words in the transition probabilities data. If -there is a match, the top 3 next words with highest probabilities are -returned. If there is no match, then the last n-1 previous words are -checked. This process is continued until the last word is checked. If -there is no match, then empty result is returned. The given words may +

    Predicts the next word given a list of previous words. It +checks the last n previous words in the transition probabilities +data, where n is equal to 1 - n-gram size of model. If there is a +match, the top 3 next words with highest probabilities are returned. +If there is no match, then the last n-1 previous words are checked. +This process is continued until the last word is checked. If there is +no match, then empty result is returned. The given words may optionally be stemmed.

    Usage

    ModelPredictor$predict_word(words, count = 3, dc = NULL)

    @@ -232,34 +259,94 @@

    Arg

    Returns

    The top 3 predicted words along with their probabilities.

    +

    Examples

    +

    # Start of environment setup code
    +# The level of detail in the information messages
    +ve <- 0
    +# The name of the folder that will contain all the files. It will be
    +# created in the current directory. NULL implies tempdir will be used
    +fn <- NULL
    +# The required files. They are default files that are part of the
    +# package
    +rf <- c("def-model.RDS")
    +# An object of class EnvManager is created
    +em <- EnvManager$new(ve = ve, "rp" = "./")
    +# The required files are downloaded
    +ed <- em$setup_env(rf, fn)
    +# End of environment setup code
    +
    +# The model file name
    +mfn <- paste0(ed, "/def-model.RDS")
    +# ModelPredictor class object is created
    +mp <- ModelPredictor$new(mf = mfn, ve = ve)
    +# The next word is predicted
    +nws <- mp$predict_word("today is", count = 10)
    +# The predicted next words are printed
    +print(nws)
    +
    +# The test environment is removed. Comment the below line, so the
    +# files generated by the function can be viewed
    +em$td_env()
    +

    +


    Method get_word_prob()

    Calculates the probability of the given word given the -previous model-1 words, where model is the maximum ngram number. It -looks up the probability of a word given n previous words. The -previous n words are converted to numeric hash using digest2int -function. The hash is looked up in a data frame of transition -probabilities. The word is converted to a number by checking its -position in a list of unique words. If the hash and the word position -were found, then the probability of the previous word and hash is -returned. If it was not found, then the hash of the n-1 previous -words is taken and the processed is repeated. If the data was not -found in the data frame, then the word probability is returned. This -is known as backoff. If the word probability could not be found then -the default probability is returned. The default probability is -calculated as 1/(N+V), Where N = number of words in corpus and V is -the number of dictionary words.

    Usage

    +previous words. The last n words are converted to numeric hash using +digest2int function. All other words are ignored. n is equal to 1 - +size of the n-gram model. The hash is looked up in a data frame of +transition probabilities. The last word is converted to a number by +checking its position in a list of unique words. If the hash and the +word position were found, then the probability of the previous word +and hash is returned. If it was not found, then the hash of the n-1 +previous words is taken and the processed is repeated. If the data +was not found in the data frame, then the word probability is +returned. This is known as back-off. If the word probability could +not be found then the default probability is returned. The default +probability is calculated as 1/(N+V), Where N = number of words in +corpus and V is the number of dictionary words.

    Usage

    ModelPredictor$get_word_prob(word, pw)

    Arguments

    -
    word

    The word whoose probability is to be calculated.

    +
    word

    The word whose probability is to be calculated.

    pw

    The previous words.

    Returns

    The probability of the word given the previous words.

    +

    Examples

    +

    # Start of environment setup code
    +# The level of detail in the information messages
    +ve <- 0
    +# The name of the folder that will contain all the files. It will be
    +# created in the current directory. NULL implies tempdir will be used
    +fn <- NULL
    +# The required files. They are default files that are part of the
    +# package
    +rf <- c("def-model.RDS")
    +# An object of class EnvManager is created
    +em <- EnvManager$new(ve = ve, "rp" = "./")
    +# The required files are downloaded
    +ed <- em$setup_env(rf, fn)
    +# End of environment setup code
    +
    +# The model file name
    +mfn <- paste0(ed, "/def-model.RDS")
    +# ModelPredictor class object is created
    +mp <- ModelPredictor$new(mf = mfn, ve = ve)
    +# The probability that the next word is "you" given the prev words
    +# "how" and "are"
    +prob <- mp$get_word_prob(word = "you", pw = c("how", "are"))
    +# The probability is printed
    +print(prob)
    +
    +# The test environment is removed. Comment the below line, so the
    +# files generated by the function can be viewed
    +em$td_env()
    +

    +


    Method clone()

    The objects of this class are cloneable with this method.

    Usage

    @@ -273,6 +360,118 @@

    Arg +

    Examples

    +
    +## ------------------------------------------------ +## Method `ModelPredictor$calc_perplexity` +## ------------------------------------------------ + +# Start of environment setup code +# The level of detail in the information messages +ve <- 0 +# The name of the folder that will contain all the files. It will be +# created in the current directory. NULL implies tempdir will be used +fn <- NULL +# The required files. They are default files that are part of the +# package +rf <- c("def-model.RDS") +# An object of class EnvManager is created +em <- EnvManager$new(ve = ve, rp = "./") +# The required files are downloaded +ed <- em$setup_env(rf, fn) +# End of environment setup code + +# The model file name +mfn <- paste0(ed, "/def-model.RDS") +# ModelPredictor class object is created +mp <- ModelPredictor$new(mf = mfn, ve = ve) +# The sentence whoose Perplexity is to be calculated +l <- "last year at this time i was preparing for a trip to rome" +# The line is split in to words +w <- strsplit(l, " ")[[1]] +# The Perplexity of the sentence is calculated +p <- mp$calc_perplexity(w) +# The sentence Perplexity is printed +print(p) +
    #> [1] 1767
    # The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed +em$td_env() + +## ------------------------------------------------ +## Method `ModelPredictor$predict_word` +## ------------------------------------------------ + +# Start of environment setup code +# The level of detail in the information messages +ve <- 0 +# The name of the folder that will contain all the files. It will be +# created in the current directory. NULL implies tempdir will be used +fn <- NULL +# The required files. They are default files that are part of the +# package +rf <- c("def-model.RDS") +# An object of class EnvManager is created +em <- EnvManager$new(ve = ve, "rp" = "./") +# The required files are downloaded +ed <- em$setup_env(rf, fn) +# End of environment setup code + +# The model file name +mfn <- paste0(ed, "/def-model.RDS") +# ModelPredictor class object is created +mp <- ModelPredictor$new(mf = mfn, ve = ve) +# The next word is predicted +nws <- mp$predict_word("today is", count = 10) +# The predicted next words are printed +print(nws) +
    #> $found +#> [1] TRUE +#> +#> $words +#> [1] "a" "the" "used" "better" "hard" "rare" "to" "best" +#> [9] "carved" "dry" +#> +#> $probs +#> [1] 0.17460317 0.11111111 0.06349206 0.03174603 0.03174603 0.03174603 +#> [7] 0.03174603 0.01587302 0.01587302 0.01587302 +#>
    +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed +em$td_env() + +## ------------------------------------------------ +## Method `ModelPredictor$get_word_prob` +## ------------------------------------------------ + +# Start of environment setup code +# The level of detail in the information messages +ve <- 0 +# The name of the folder that will contain all the files. It will be +# created in the current directory. NULL implies tempdir will be used +fn <- NULL +# The required files. They are default files that are part of the +# package +rf <- c("def-model.RDS") +# An object of class EnvManager is created +em <- EnvManager$new(ve = ve, "rp" = "./") +# The required files are downloaded +ed <- em$setup_env(rf, fn) +# End of environment setup code + +# The model file name +mfn <- paste0(ed, "/def-model.RDS") +# ModelPredictor class object is created +mp <- ModelPredictor$new(mf = mfn, ve = ve) +# The probability that the next word is "you" given the prev words +# "how" and "are" +prob <- mp$get_word_prob(word = "you", pw = c("how", "are")) +# The probability is printed +print(prob) +
    #> [1] 0.0024581
    +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed +em$td_env() +
    @@ -129,38 +129,41 @@
    -

    It provides a method for generating transition probabilities for the given -n-gram size. It also provides a method for generating the combined transition -probabilities data for n-gram sizes from 1 to the given size. The combined -transition probabilities data can be used to implement back-off.

    +

    It provides a method for generating transition probabilities for +the given n-gram size. It also provides a method for generating the combined +transition probabilities data for n-gram sizes from 1 to the given size. The +combined transition probabilities data can be used to implement back-off.

    Details

    -

    It provides a method for generating n-gram transition probabilities. It reads -ngram frequencies from an input text file that is generated by the -TokenGenerator class. It parses each ngram into a prefix, a next word and the -next word frequency. Maximum Likelihood count is used to generated the next -word probabilities. The n-gram prefix is converted to a numeric hash. The -next word is replaced with the position of the next word in the list of all -words. The transition probabilities data is stored in a file. Another method -is provided that combines the transition probabilities for n-grams of size 1 -to the given size. The combined transition probabilities can be saved as a -data frame. This file may be regarded as a n-gram model. The name of the file -is model-n.RDS, where n is the n-gram number. By combining the transition +

    It provides a method for generating n-gram transition probabilities. +It reads n-gram frequencies from an input text file that is generated by the +TokenGenerator class.

    +

    It parses each n-gram into a prefix, a next word, the next word frequency and +the next word probability. Maximum Likelihood count is used to generate the +next word probabilities.

    +

    Each n-gram prefix is converted to a numeric hash using the digest2int +function. The next word is replaced with the position of the next word in the +list of all words. The transition probabilities data is stored as a dataframe +in a file.

    +

    Another method is provided that combines the transition probabilities for +n-grams of size 1 to the given size. The combined transition probabilities +can be saved to a file as a data frame. This file may be regarded as a +completed self contained n-gram model. By combining the transition probabilities of n-grams, back-off may be used to evaluate word probabilities or predict the next word.

    Super class

    -

    wordpredictor::TextFileProcessor -> TPGenerator

    +

    wordpredictor::Base -> TPGenerator

    Methods

    @@ -188,7 +191,7 @@

    Arg
    opts

    The options for generating the transition probabilities.

    • save_tp. If the data should be saved.

    • -
    • n. The ngram size.

    • +
    • n. The n-gram size.

    • dir. The directory containing the input and output files.

    • format. The format for the output. There are two options.

      • plain. The data is stored in plain text.

      • @@ -196,27 +199,58 @@

        Arg

    -
    ve

    If progress information should be displayed.

    +
    ve

    The level of detail in the information messages.


    Method generate_tp()

    -

    It combines the transition probabilities for n-grams -less than or equal to the given n-gram size. It first generates -the transition probabilities for each ngram size from 1 to the given -size. The transition probabilities are then combined into a single -data frame and saved to the output folder that is given as parameter -to the current object. By combining the transition probabilities for -all n-gram sizes less than n, back-off can be used to calculate next -word probabilities or predict the next word.

    Usage

    +

    It first generates the transition probabilities for each +n-gram of size from 1 to the given size. The transition probabilities +are then combined into a single data frame and saved to the output +folder that is given as parameter to the current object.

    +

    By combining the transition probabilities for all n-gram sizes from 1 +to n, back-off can be used to calculate next word probabilities or +predict the next word.

    Usage

    TPGenerator$generate_tp()

    +

    Examples

    +

    # Start of environment setup code
    +# The level of detail in the information messages
    +ve <- 0
    +# The name of the folder that will contain all the files. It will be
    +# created in the current directory. NULL implies tempdir will be used
    +fn <- NULL
    +# The required files. They are default files that are part of the
    +# package
    +rf <- c("n1.RDS", "n2.RDS", "n3.RDS", "n4.RDS")
    +# An object of class EnvManager is created
    +em <- EnvManager$new(ve = ve, rp = "./")
    +# The required files are downloaded
    +ed <- em$setup_env(rf, fn)
    +# End of environment setup code
    +
    +# The list of output files
    +fns <- c("words", "model-4", "tp2", "tp3", "tp4")
    +
    +# The TPGenerator object is created
    +tp <- TPGenerator$new(opts = list(n = 4, dir = ed), ve = ve)
    +# The combined transition probabilities are generated
    +tp$generate_tp()
    +
    +# The test environment is removed. Comment the below line, so the
    +# files generated by the function can be viewed
    +em$td_env()
    +

    +


    Method generate_tp_for_n()

    -

    It reads ngram token frequencies from an input text -file. It generates a data frame containing the prefix, next word -and next word frequency. The data frame may be saved to a file as -plain text or as a R obj. For n = 1, the list of words is saved.

    Usage

    +

    It generates the transition probabilities table for the +given n-gram size. It first reads n-gram token frequencies from an +input text file.

    +

    It then generates a data frame whose columns are the +n-gram prefix, next word and next word frequency. The data frame may +be saved to a file as plain text or as a R obj. If n = 1, then the +list of words is saved.

    Usage

    TPGenerator$generate_tp_for_n(n)

    Arguments

    @@ -237,6 +271,39 @@

    Arg +

    Examples

    +
    +## ------------------------------------------------ +## Method `TPGenerator$generate_tp` +## ------------------------------------------------ + +# Start of environment setup code +# The level of detail in the information messages +ve <- 0 +# The name of the folder that will contain all the files. It will be +# created in the current directory. NULL implies tempdir will be used +fn <- NULL +# The required files. They are default files that are part of the +# package +rf <- c("n1.RDS", "n2.RDS", "n3.RDS", "n4.RDS") +# An object of class EnvManager is created +em <- EnvManager$new(ve = ve, rp = "./") +# The required files are downloaded +ed <- em$setup_env(rf, fn) +# End of environment setup code + +# The list of output files +fns <- c("words", "model-4", "tp2", "tp3", "tp4") + +# The TPGenerator object is created +tp <- TPGenerator$new(opts = list(n = 4, dir = ed), ve = ve) +# The combined transition probabilities are generated +tp$generate_tp() + +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed +em$td_env() +
    @@ -127,13 +127,13 @@
    -

    It generates ngram tokens along with their frequencies. The data +

    It generates n-gram tokens along with their frequencies. The data may be saved to a file in plain text format or as a R object.

    @@ -141,7 +141,7 @@

    It generates ngrams of given size from an input text file

    Super class

    -

    wordpredictor::TextFileProcessor -> TokenGenerator

    +

    wordpredictor::Base -> TokenGenerator

    Methods

    @@ -167,11 +167,11 @@

    Arg

    fn

    The path to the input file.

    -
    opts

    The options for generating the ngram tokens. +

    opts

    The options for generating the n-gram tokens.

      -
    • n. The ngram size.

    • -
    • save_ngrams. If the ngram data should be saved.

    • -
    • min_freq. All ngrams with frequency less than min_freq are +

    • n. The n-gram size.

    • +
    • save_ngrams. If the n-gram data should be saved.

    • +
    • min_freq. All n-grams with frequency less than min_freq are ignored.

    • line_count. The number of lines to process at a time.

    • stem_words. If words should be transformed to their stems.

    • @@ -182,19 +182,51 @@

      Arg

    -
    ve

    Indicates if progress information should be displayed.

    +
    ve

    The level of detail in the information messages.


    Method generate_tokens()

    -

    It generates ngram tokens and their frequencies from the +

    It generates n-gram tokens and their frequencies from the given file name. The tokens may be saved to a text file as plain text or a R object.

    Usage

    TokenGenerator$generate_tokens()

    Returns

    -

    The data frame containing ngram tokens along with their +

    The data frame containing n-gram tokens along with their frequencies.

    +

    Examples

    +

    # Start of environment setup code
    +# The level of detail in the information messages
    +ve <- 0
    +# The name of the folder that will contain all the files. It will be
    +# created in the current directory. NULL implies tempdir will be used
    +fn <- NULL
    +# The required files. They are default files that are part of the
    +# package
    +rf <- c("test-clean.txt")
    +# An object of class EnvManager is created
    +em <- EnvManager$new(ve = ve, rp = "./")
    +# The required files are downloaded
    +ed <- em$setup_env(rf, fn)
    +# End of environment setup code
    +
    +# The n-gram size
    +n <- 4
    +# The test file name
    +tfn <- paste0(ed, "/test-clean.txt")
    +# The ngram number is set
    +tg_opts <- list("n" = n, "save_ngrams" = TRUE, "dir" = ed)
    +# The TokenGenerator object is created
    +tg <- TokenGenerator$new(tfn, tg_opts, ve = ve)
    +# The ngram tokens are generated
    +tg$generate_tokens()
    +
    +# The test environment is removed. Comment the below line, so the
    +# files generated by the function can be viewed
    +em$td_env()
    +

    +


    Method clone()

    The objects of this class are cloneable with this method.

    Usage

    @@ -208,6 +240,42 @@

    Arg +

    Examples

    +
    +## ------------------------------------------------ +## Method `TokenGenerator$generate_tokens` +## ------------------------------------------------ + +# Start of environment setup code +# The level of detail in the information messages +ve <- 0 +# The name of the folder that will contain all the files. It will be +# created in the current directory. NULL implies tempdir will be used +fn <- NULL +# The required files. They are default files that are part of the +# package +rf <- c("test-clean.txt") +# An object of class EnvManager is created +em <- EnvManager$new(ve = ve, rp = "./") +# The required files are downloaded +ed <- em$setup_env(rf, fn) +# End of environment setup code + +# The n-gram size +n <- 4 +# The test file name +tfn <- paste0(ed, "/test-clean.txt") +# The ngram number is set +tg_opts <- list("n" = n, "save_ngrams" = TRUE, "dir" = ed) +# The TokenGenerator object is created +tg <- TokenGenerator$new(tfn, tg_opts, ve = ve) +# The ngram tokens are generated +tg$generate_tokens() + +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed +em$td_env() +
    @@ -150,10 +150,16 @@

    Base

    + +

    Base class for all other classes

    + +

    DataAnalyzer

    -

    Allows analyzing n-gram data

    +

    Analyzes input text files and n-gram token files

    @@ -165,50 +171,49 @@

    DataSampler

    -

    It allows generating data samples from text files.

    +

    Generates data samples from text files

    + + + +

    EnvManager

    + +

    Allows managing the test environment

    Model

    -

    The Model class represents n-gram models. An instance of the class is a -single n-gram model.

    +

    Represents n-gram models

    ModelEvaluator

    -

    It performs extrinsic and intrinsic evaluation of a n-gram model

    +

    Evaluates performance of n-gram models

    ModelGenerator

    -

    It is used to generate n-gram models for the given data file.

    +

    Generates n-gram models from a text file

    ModelPredictor

    -

    It is used to evaluate the accuracy and performance of the model.

    +

    Allows predicting text, calculating word probabilities and Perplexity

    TPGenerator

    -

    It is used to generate transition probabilities for n-grams.

    - - - -

    TextFileProcessor

    - -

    Base class for text file processing

    +

    Generates transition probabilities for n-grams

    TokenGenerator

    -

    It generates ngrams of given size from an input text file

    +

    Generates n-grams from text files

    diff --git a/docs/reference/wordpredictor-package.html b/docs/reference/wordpredictor-package.html new file mode 100644 index 0000000..457ff6d --- /dev/null +++ b/docs/reference/wordpredictor-package.html @@ -0,0 +1,200 @@ + + + + + + + + +wordpredictor: Develop Text Prediction Models Based on N-Grams — wordpredictor-package • Word Predictor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    The wordpredictor package allows developing n-gram models for text + prediction. It provides methods for data cleaning, data sampling, + tokenization, model generation, model evaluation and word prediction. + For information on how n-gram models work we referred to: Jurafsky, Daniel & + Martin, James. (2008). "Speech and Language Processing: An Introduction to + Natural Language Processing, Computational Linguistics, and Speech + Recognition" <https://web.stanford.edu/~jurafsky/slp3/3.pdf>. + For optimizing R code and using R6 classes we reffered to Hadley Wickham, + "Advanced R" <https://adv-r.hadley.nz/r6.html>. For writing R extensions we + refer to Hadley Wickham and Jenny Bryan, "R Packages", + <https://r-pkgs.org/index.html>.

    +
    + + + +

    See also

    + + +

    Author

    + +

    Maintainer: Nadir Latif pakjiddat@gmail.com (ORCID)

    + +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 723bba1..bbdcf9c 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -3,6 +3,9 @@ https://pakjiddat.github.io/word-predictor/index.html + + https://pakjiddat.github.io/word-predictor/reference/Base.html + https://pakjiddat.github.io/word-predictor/reference/DataAnalyzer.html @@ -12,6 +15,9 @@ https://pakjiddat.github.io/word-predictor/reference/DataSampler.html + + https://pakjiddat.github.io/word-predictor/reference/EnvManager.html + https://pakjiddat.github.io/word-predictor/reference/Model.html @@ -28,10 +34,10 @@ https://pakjiddat.github.io/word-predictor/reference/TPGenerator.html - https://pakjiddat.github.io/word-predictor/reference/TextFileProcessor.html + https://pakjiddat.github.io/word-predictor/reference/TokenGenerator.html - https://pakjiddat.github.io/word-predictor/reference/TokenGenerator.html + https://pakjiddat.github.io/word-predictor/reference/wordpredictor-package.html https://pakjiddat.github.io/word-predictor/articles/features.html diff --git a/inst/CITATION b/inst/CITATION index 77890d6..211c19e 100644 --- a/inst/CITATION +++ b/inst/CITATION @@ -6,5 +6,9 @@ citEntry( author = "Nadir Latif", year = "2021", note = "R package version 1.0.0", - url = "https://github.com/pakjiddat/word-predictor" + url = "https://github.com/pakjiddat/word-predictor", + textVersion = paste( + + ) ) + diff --git a/inst/examples/clean-file.R b/inst/examples/clean-file.R index cad81b7..49ccdd1 100644 --- a/inst/examples/clean-file.R +++ b/inst/examples/clean-file.R @@ -1,6 +1,6 @@ # Start of environment setup code # The level of detail in the information messages -ve <- 0 +ve <- 2 # The name of the folder that will contain all the files. It will be created in # the current directory. NULL implies tempdir will be used. fn <- NULL diff --git a/inst/examples/clean-lines.R b/inst/examples/clean-lines.R index 29bd821..ed9a553 100644 --- a/inst/examples/clean-lines.R +++ b/inst/examples/clean-lines.R @@ -1,6 +1,6 @@ # Start of environment setup code # The level of detail in the information messages -ve <- 0 +ve <- 2 # Test data is read l <- c( diff --git a/inst/examples/compare-performance.R b/inst/examples/compare-performance.R index f0019bb..ac8a912 100644 --- a/inst/examples/compare-performance.R +++ b/inst/examples/compare-performance.R @@ -1,6 +1,6 @@ # Start of environment setup code # The level of detail in the information messages -ve <- 0 +ve <- 2 # The name of the folder that will contain all the files. It will be created in # the current directory. NULL implies tempdir will be used. fn <- NULL diff --git a/inst/examples/evaluate-performance.R b/inst/examples/evaluate-performance.R index 80eb935..de5f9af 100644 --- a/inst/examples/evaluate-performance.R +++ b/inst/examples/evaluate-performance.R @@ -1,6 +1,6 @@ # Start of environment setup code # The level of detail in the information messages -ve <- 0 +ve <- 2 # The name of the folder that will contain all the files. It will be created in # the current directory. NULL implies tempdir will be used fn <- NULL diff --git a/inst/examples/extrinsic-evaluation.R b/inst/examples/extrinsic-evaluation.R index da3819f..dac35bf 100644 --- a/inst/examples/extrinsic-evaluation.R +++ b/inst/examples/extrinsic-evaluation.R @@ -1,6 +1,6 @@ # Start of environment setup code # The level of detail in the information messages -ve <- 0 +ve <- 2 # The name of the folder that will contain all the files. It will be created in # the current directory. NULL implies tempdir will be used fn <- NULL diff --git a/inst/examples/generate-data.R b/inst/examples/generate-data.R index 0d224c6..48c345e 100644 --- a/inst/examples/generate-data.R +++ b/inst/examples/generate-data.R @@ -1,6 +1,6 @@ # Start of environment setup code # The level of detail in the information messages -ve <- 0 +ve <- 2 # The name of the folder that will contain all the files. It will be created in # the current directory. NULL implies tempdir will be used. fn <- NULL diff --git a/inst/examples/generate-model.R b/inst/examples/generate-model.R index bea3a77..c15968c 100644 --- a/inst/examples/generate-model.R +++ b/inst/examples/generate-model.R @@ -1,6 +1,6 @@ # Start of environment setup code # The level of detail in the information messages -ve <- 0 +ve <- 2 # The name of the folder that will contain all the files. It will be created in # the current directory. NULL implies tempdir will be used fn <- NULL diff --git a/inst/examples/generate-sample.R b/inst/examples/generate-sample.R index f955af0..8f4dc61 100644 --- a/inst/examples/generate-sample.R +++ b/inst/examples/generate-sample.R @@ -1,6 +1,6 @@ # Start of environment setup code # The level of detail in the information messages -ve <- 0 +ve <- 2 # The name of the folder that will contain all the files. It will be created in # the current directory. NULL implies tempdir will be used fn <- NULL diff --git a/inst/examples/generate-tokens.R b/inst/examples/generate-tokens.R index eb3737d..4888177 100644 --- a/inst/examples/generate-tokens.R +++ b/inst/examples/generate-tokens.R @@ -1,6 +1,6 @@ # Start of environment setup code # The level of detail in the information messages -ve <- 0 +ve <- 2 # The name of the folder that will contain all the files. It will be created in # the current directory. NULL implies tempdir will be used fn <- NULL diff --git a/inst/examples/generate-tp.R b/inst/examples/generate-tp.R index ae82ae7..d8e30c1 100644 --- a/inst/examples/generate-tp.R +++ b/inst/examples/generate-tp.R @@ -1,6 +1,6 @@ # Start of environment setup code # The level of detail in the information messages -ve <- 0 +ve <- 2 # The name of the folder that will contain all the files. It will be created in # the current directory. NULL implies tempdir will be used fn <- NULL diff --git a/inst/examples/get-file-info.R b/inst/examples/get-file-info.R index df02249..32fa76c 100644 --- a/inst/examples/get-file-info.R +++ b/inst/examples/get-file-info.R @@ -1,6 +1,6 @@ # Start of environment setup code # The level of detail in the information messages -ve <- 0 +ve <- 2 # The name of the folder that will contain all the files. It will be created in # the current directory. NULL implies tempdir will be used fn <- NULL diff --git a/inst/examples/get-n-grams.R b/inst/examples/get-n-grams.R index 269caff..36947ef 100644 --- a/inst/examples/get-n-grams.R +++ b/inst/examples/get-n-grams.R @@ -1,6 +1,6 @@ # Start of environment setup code # The level of detail in the information messages -ve <- 0 +ve <- 2 # The name of the folder that will contain all the files. It will be created in # the current directory. NULL implies tempdir will be used fn <- NULL diff --git a/inst/examples/get-word-prob.R b/inst/examples/get-word-prob.R index f6bc556..c8043ff 100644 --- a/inst/examples/get-word-prob.R +++ b/inst/examples/get-word-prob.R @@ -1,6 +1,6 @@ # Start of environment setup code # The level of detail in the information messages -ve <- 0 +ve <- 2 # The name of the folder that will contain all the files. It will be created in # the current directory. NULL implies tempdir will be used fn <- NULL diff --git a/inst/examples/intrinsic-evaluation.R b/inst/examples/intrinsic-evaluation.R index 0372fd6..18a8fbd 100644 --- a/inst/examples/intrinsic-evaluation.R +++ b/inst/examples/intrinsic-evaluation.R @@ -1,6 +1,6 @@ # Start of environment setup code # The level of detail in the information messages -ve <- 0 +ve <- 2 # The name of the folder that will contain all the files. It will be created in # the current directory. NULL implies tempdir will be used fn <- NULL diff --git a/inst/examples/plot-n-gram-stats.R b/inst/examples/plot-n-gram-stats.R index 4db5d93..312562a 100644 --- a/inst/examples/plot-n-gram-stats.R +++ b/inst/examples/plot-n-gram-stats.R @@ -1,6 +1,6 @@ # Start of environment setup code # The level of detail in the information messages -ve <- 0 +ve <- 2 # The name of the folder that will contain all the files. It will be created in # the current directory. NULL value implies tempdir will be used. fn <- NULL diff --git a/inst/examples/predict-word.R b/inst/examples/predict-word.R index beb648c..bc7a56e 100644 --- a/inst/examples/predict-word.R +++ b/inst/examples/predict-word.R @@ -1,6 +1,6 @@ # Start of environment setup code # The level of detail in the information messages -ve <- 0 +ve <- 2 # The name of the folder that will contain all the files. It will be created in # the current directory. NULL implies tempdir will be used fn <- NULL diff --git a/man/DataAnalyzer.Rd b/man/DataAnalyzer.Rd index f102e50..5cf06d2 100644 --- a/man/DataAnalyzer.Rd +++ b/man/DataAnalyzer.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/data-analyzer.R \name{DataAnalyzer} \alias{DataAnalyzer} -\title{It allows analyzing input text files and n-gram token files} +\title{Analyzes input text files and n-gram token files} \description{ It provides a method that returns information about text files, such as number of lines and number of words. It also provides a method that displays @@ -102,7 +102,8 @@ ve <- 0 # The name of the folder that will contain all the files. It will be # created in the current directory. NULL implies tempdir will be used fn <- NULL -# The required files. They are default files that are part of the package +# The required files. They are default files that are part of the +# package rf <- c("n2.RDS") # An object of class EnvManager is created em <- EnvManager$new(ve = ve, rp = "./") @@ -332,7 +333,8 @@ ve <- 0 # The name of the folder that will contain all the files. It will be # created in the current directory. NULL implies tempdir will be used fn <- NULL -# The required files. They are default files that are part of the package +# The required files. They are default files that are part of the +# package rf <- c("n2.RDS") # An object of class EnvManager is created em <- EnvManager$new(ve = ve, rp = "./") diff --git a/man/DataSampler.Rd b/man/DataSampler.Rd index a501fa0..008cc87 100644 --- a/man/DataSampler.Rd +++ b/man/DataSampler.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/data-sampler.R \name{DataSampler} \alias{DataSampler} -\title{It allows generating data samples from text files.} +\title{Generates data samples from text files} \description{ It provides a method for generating training, testing and validation data sets from a given input text file. @@ -57,10 +57,12 @@ em$td_env() # Start of environment setup code # The level of detail in the information messages ve <- 0 -# The name of the folder that will contain all the files. It will be created in -# the current directory. NULL implies tempdir will be used. +# The name of the folder that will contain all the files. It will be +# created in the current directory. NULL implies tempdir will be +# used fn <- NULL -# The required files. They are default files that are part of the package +# The required files. They are default files that are part of the +# package rf <- c("input.txt") # An object of class EnvManager is created em <- EnvManager$new(ve = ve) @@ -82,8 +84,8 @@ ds$generate_data( ) ) -# The test environment is removed. Comment the below line, so the files -# generated by the function can be viewed +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed em$td_env() } \section{Super class}{ @@ -225,10 +227,12 @@ attribute.} \preformatted{# Start of environment setup code # The level of detail in the information messages ve <- 0 -# The name of the folder that will contain all the files. It will be created in -# the current directory. NULL implies tempdir will be used. +# The name of the folder that will contain all the files. It will be +# created in the current directory. NULL implies tempdir will be +# used fn <- NULL -# The required files. They are default files that are part of the package +# The required files. They are default files that are part of the +# package rf <- c("input.txt") # An object of class EnvManager is created em <- EnvManager$new(ve = ve) @@ -250,8 +254,8 @@ ds$generate_data( ) ) -# The test environment is removed. Comment the below line, so the files -# generated by the function can be viewed +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed em$td_env() } \if{html}{\out{

    }} diff --git a/man/EnvManager.Rd b/man/EnvManager.Rd index a17506d..a738fcc 100644 --- a/man/EnvManager.Rd +++ b/man/EnvManager.Rd @@ -2,8 +2,7 @@ % Please edit documentation in R/env-manager.R \name{EnvManager} \alias{EnvManager} -\title{It provides methods for setting up the test environment and reading external -data} +\title{Allows managing the test environment} \description{ This class provides a method for creating directories in the tempdir folder for testing purposes. It also provides a method for reading files from the diff --git a/man/Model.Rd b/man/Model.Rd index dd0af17..a848cc0 100644 --- a/man/Model.Rd +++ b/man/Model.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/model.R \name{Model} \alias{Model} -\title{This class represents n-gram models} +\title{Represents n-gram models} \description{ The Model class represents n-gram models. An instance of the class is a single n-gram model. The attributes of this class are used to store n-gram @@ -41,6 +41,7 @@ performance. \item \href{#method-new}{\code{Model$new()}} \item \href{#method-load_model}{\code{Model$load_model()}} \item \href{#method-get_config}{\code{Model$get_config()}} +\item \href{#method-get_size}{\code{Model$get_size()}} \item \href{#method-clone}{\code{Model$clone()}} } } @@ -130,6 +131,20 @@ The configuration value. } } \if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-get_size}{}}} +\subsection{Method \code{get_size()}}{ +It returns the size of the current object. The object +size is calculated as the sum of sizes of the object attributes. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{Model$get_size()}\if{html}{\out{
    }} +} + +\subsection{Returns}{ +The size of the object in bytes. +} +} +\if{html}{\out{
    }} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-clone}{}}} \subsection{Method \code{clone()}}{ diff --git a/man/ModelEvaluator.Rd b/man/ModelEvaluator.Rd index 8913292..8a26fc5 100644 --- a/man/ModelEvaluator.Rd +++ b/man/ModelEvaluator.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/model-evaluator.R \name{ModelEvaluator} \alias{ModelEvaluator} -\title{It performs performance evaluation of n-gram models} +\title{Evaluates performance of n-gram models} \description{ It provides methods for performing extrinsic and intrinsic evaluation of a n-gram model. It also provides a method for comparing @@ -89,8 +89,8 @@ stats <- me$evaluate_performance(lc = 20, fn = vfn) # The evaluation stats are printed print(stats) -# The test environment is removed. Comment the below line, so the files -# generated by the function can be viewed +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed em$td_env() ## ------------------------------------------------ @@ -103,7 +103,8 @@ ve <- 0 # The name of the folder that will contain all the files. It will be # created in the current directory. NULL implies tempdir will be used fn <- NULL -# The required files. They are default files that are part of the package +# The required files. They are default files that are part of the +# package rf <- c("def-model.RDS", "validate-clean.txt") # An object of class EnvManager is created em <- EnvManager$new(ve = ve, rp = "./") @@ -123,8 +124,8 @@ stats <- me$intrinsic_evaluation(lc = 20, fn = vfn) # The evaluation stats are printed print(stats) -# The test environment is removed. Comment the below line, so the files -# generated by the function can be viewed +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed em$td_env() ## ------------------------------------------------ @@ -158,8 +159,8 @@ stats <- me$extrinsic_evaluation(lc = 100, fn = vfn) # The evaluation stats are printed print(stats) -# The test environment is removed. Comment the below line, so the files -# generated by the function can be viewed +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed em$td_env() } \section{Super class}{ @@ -358,8 +359,8 @@ stats <- me$evaluate_performance(lc = 20, fn = vfn) # The evaluation stats are printed print(stats) -# The test environment is removed. Comment the below line, so the files -# generated by the function can be viewed +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed em$td_env() } \if{html}{\out{
    }} @@ -401,7 +402,8 @@ ve <- 0 # The name of the folder that will contain all the files. It will be # created in the current directory. NULL implies tempdir will be used fn <- NULL -# The required files. They are default files that are part of the package +# The required files. They are default files that are part of the +# package rf <- c("def-model.RDS", "validate-clean.txt") # An object of class EnvManager is created em <- EnvManager$new(ve = ve, rp = "./") @@ -421,8 +423,8 @@ stats <- me$intrinsic_evaluation(lc = 20, fn = vfn) # The evaluation stats are printed print(stats) -# The test environment is removed. Comment the below line, so the files -# generated by the function can be viewed +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed em$td_env() } \if{html}{\out{}} @@ -487,8 +489,8 @@ stats <- me$extrinsic_evaluation(lc = 100, fn = vfn) # The evaluation stats are printed print(stats) -# The test environment is removed. Comment the below line, so the files -# generated by the function can be viewed +# The test environment is removed. Comment the below line, so the +# files generated by the function can be viewed em$td_env() } \if{html}{\out{}} diff --git a/man/ModelGenerator.Rd b/man/ModelGenerator.Rd index f6642e0..0347d31 100644 --- a/man/ModelGenerator.Rd +++ b/man/ModelGenerator.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/model-generator.R \name{ModelGenerator} \alias{ModelGenerator} -\title{It is used to generate n-gram models from a text file} +\title{Generates n-gram models from a text file} \description{ It provides a method for generating n-gram models. The n-gram models may be customized by specifying data cleaning and tokenization options. diff --git a/man/ModelPredictor.Rd b/man/ModelPredictor.Rd index 285dda1..0f28ead 100644 --- a/man/ModelPredictor.Rd +++ b/man/ModelPredictor.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/model-predictor.R \name{ModelPredictor} \alias{ModelPredictor} -\title{It allows predicting text, calculating word probabilities and Perplexity} +\title{Allows predicting text, calculating word probabilities and Perplexity} \description{ It provides a method for predicting the new word given a set of previous words. It also provides a method for calculating the Perplexity diff --git a/man/TPGenerator.Rd b/man/TPGenerator.Rd index 4ab57d6..4f73b98 100644 --- a/man/TPGenerator.Rd +++ b/man/TPGenerator.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/tp-generator.R \name{TPGenerator} \alias{TPGenerator} -\title{It is used to generate transition probabilities for n-grams.} +\title{Generates transition probabilities for n-grams} \description{ It provides a method for generating transition probabilities for the given n-gram size. It also provides a method for generating the combined diff --git a/man/TokenGenerator.Rd b/man/TokenGenerator.Rd index b39678f..7db8db7 100644 --- a/man/TokenGenerator.Rd +++ b/man/TokenGenerator.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/token-generator.R \name{TokenGenerator} \alias{TokenGenerator} -\title{It generates n-grams of given size from an input text file} +\title{Generates n-grams from text files} \description{ It generates n-gram tokens along with their frequencies. The data may be saved to a file in plain text format or as a R object. diff --git a/man/wordpredictor-package.Rd b/man/wordpredictor-package.Rd index f68f05d..8d67946 100644 --- a/man/wordpredictor-package.Rd +++ b/man/wordpredictor-package.Rd @@ -8,13 +8,13 @@ \description{ The wordpredictor package allows developing n-gram models for text prediction. It provides methods for data cleaning, data sampling, - tokenization, model generation, model evaluation and word prediction. For - information on how n-gram models work we referred to: Jurafsky, Daniel & + tokenization, model generation, model evaluation and word prediction. + For information on how n-gram models work we referred to: Jurafsky, Daniel & Martin, James. (2008). "Speech and Language Processing: An Introduction to Natural Language Processing, Computational Linguistics, and Speech - Recognition" . + Recognition" . For optimizing R code and using R6 classes we reffered to Hadley Wickham, - "Advanced R" . For writing R extensions we + "Advanced R" . For writing R extensions we refer to Hadley Wickham and Jenny Bryan, "R Packages", . } diff --git a/tests/testthat/notes.txt b/tests/testthat/notes.txt index 8b20f6f..c63fa59 100644 --- a/tests/testthat/notes.txt +++ b/tests/testthat/notes.txt @@ -3,5 +3,5 @@ Information about test data files: 1. input.txt is generated from test a 2. test.txt and validate.txt are generated from test b 3. test-clean.txt and validate-clean.txt are generated from test c -4. def-model.RDS is generated from test h +4. def-model.RDS is generated from tests a and h 5. n1.RDS, n2.RDS, n3.RDS and n4.RDS are generated from test e diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R index 01bf5fa..ea1fdbc 100644 --- a/tests/testthat/setup.R +++ b/tests/testthat/setup.R @@ -24,7 +24,7 @@ for (fn in fl) { # If level of verbosity in the information messages ve <- 0 # The action to take after the test ends -ea <- 't' +ea <- "t" # The option are set options("wordpredictor" = list( diff --git a/vignettes/man/figures/README-tokenization-2-1.png b/vignettes/man/figures/README-tokenization-2-1.png index db5263d26059d788a570a796f9e95749925c5648..b3ff5c7f38ce4723beb4495ce49128b6469b5a18 100644 GIT binary patch literal 33306 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 literal 5554 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 YxAG#lb+$C|f1p4r3Yz!J~p=|IZ|1$Zu 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 diff --git a/vignettes/references.bib b/vignettes/references.bib index 8e36316..33f2f02 100644 --- a/vignettes/references.bib +++ b/vignettes/references.bib @@ -76,3 +76,11 @@ @inproceedings{intro-to-natural-lang-proc year = "2014", url = "http://www.cs.cornell.edu/courses/cs4740/2014sp/lectures/smoothing+backoff.pdf" } + +@misc{enwiki:1022965742, + author = "{Wikipedia contributors}", + title = "Perplexity --- {Wikipedia}{,} The Free Encyclopedia", + year = "2021", + url = "https://en.wikipedia.org/w/index.php?title=Perplexity&oldid=1022965742", + note = "[Online; accessed 8-June-2021]" +}