diff --git a/.all-contributorsrc b/.all-contributorsrc index d014ad73a..8c7d7b544 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1249,6 +1249,74 @@ "bug", "code" ] + }, + { + "login": "eltociear", + "name": "Ikko Ashimine", + "avatar_url": "https://avatars.githubusercontent.com/u/22633385?v=4", + "profile": "https://bandism.net/", + "contributions": [ + "doc" + ] + }, + { + "login": "Mudasar-Makandar", + "name": "Mudasar-Makandar", + "avatar_url": "https://avatars.githubusercontent.com/u/46401916?v=4", + "profile": "https://github.com/Mudasar-Makandar", + "contributions": [ + "bug", + "code" + ] + }, + { + "login": "amirfeqhi", + "name": "Amir Feqhi", + "avatar_url": "https://avatars.githubusercontent.com/u/26363996?v=4", + "profile": "https://github.com/amirfeqhi", + "contributions": [ + "code" + ] + }, + { + "login": "danidask", + "name": "DasK", + "avatar_url": "https://avatars.githubusercontent.com/u/9405129?v=4", + "profile": "https://github.com/danidask", + "contributions": [ + "code", + "ideas" + ] + }, + { + "login": "asvsfs", + "name": "Amir", + "avatar_url": "https://avatars.githubusercontent.com/u/119840?v=4", + "profile": "https://github.com/asvsfs", + "contributions": [ + "code", + "example" + ] + }, + { + "login": "lindapaiste", + "name": "lindapaiste", + "avatar_url": "https://avatars.githubusercontent.com/u/28965286?v=4", + "profile": "http://lindapaiste.com", + "contributions": [ + "code", + "ideas", + "bug" + ] + }, + { + "login": "emwdx", + "name": "Evan Weinberg", + "avatar_url": "https://avatars.githubusercontent.com/u/1531831?v=4", + "profile": "http://evanweinberg.com", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index ad49e5f9b..23cb9c96e 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,14 @@ Before getting started with ml5.js, review our [Code of Conduct](https://github. + + + + + + + + @@ -63,11 +71,11 @@ Before getting started with ml5.js, review our [Code of Conduct](https://github.
-* You can use the latest version (0.9.8) by adding it to the head section of your HTML document: +* You can use the latest version (0.10.6) by adding it to the head section of your HTML document: -**v0.9.8** +**v0.10.6** - +
@@ -93,6 +101,14 @@ Before getting started with ml5.js, review our [Code of Conduct](https://github. + + + + + + + + @@ -130,11 +146,11 @@ For example: ## Resources -- [Getting Started](https://ml5js.org/getting-started/) -- [API Reference](https://ml5js.org/reference/) +- [Getting Started](https://learn.ml5js.org/) +- [API Reference](https://learn.ml5js.org/#/reference/index) - [Examples](https://github.com/ml5js/ml5-library/tree/main/examples) - [Community](https://ml5js.org/community) -- [FAQ](https://ml5js.org/getting-started/faq/) +- [FAQ](https://learn.ml5js.org/#/faq) ## Standalone Examples @@ -321,6 +337,15 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
---|---|---|---|---|---|---|---|---|---|
index.js | +
+
+ |
+ 69.34% | +95/137 | +63.63% | +21/33 | +63.63% | +14/22 | +70% | +91/130 | +
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 | + + + + + + + + + + + + + + + + +1x +1x +1x + + + + + + + + + + + + + + + +1x + + + + + + +1x +1x +1x +1x + + + + + +1x +1x +1x +1x +1x + + + + + + +1x + + + + + + + + + + + + + + + + +1x +1x +1x +8x +4x +2x +2x + +2x + +4x +2x +1x + +1x + + +2x + + +1x +1x +1x + + + +1x +1x +1x +1x +1x + + + + + + +1x +1x +1x + +1x +2x +108x + + + + + + + +2x + + +1x +2x +2x +2x + + +1x + + + +2x +2x +2x +2x +2x +2x +2x + + +2x +2x +2x + +2x +26x + + +2x +2x + +2x +54x +54x +54x + +54x +54x +54x + + + + +54x +54x + +54x +54x +54x +54x +54x +54x + +54x +24x + +30x +30x + + + +2x +2x +1861x +30x +30x + + +2x +2x + + + + + + + + + +2x + + + + + + + + + + + + + + + + + + + + + + +2x +2x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1x + + + | // Copyright (c) 2018 ml5 +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +/* eslint prefer-destructuring: ["error", {AssignmentExpression: {array: false}}] */ +/* eslint no-await-in-loop: "off" */ +/* +A LSTM Generator: Run inference mode for a pre-trained LSTM. +*/ + +import * as tf from "@tensorflow/tfjs"; +import axios from "axios"; +import sampleFromDistribution from "./../utils/sample"; +import CheckpointLoader from "../utils/checkpointLoader"; +import callCallback from "../utils/callcallback"; + +const regexCell = /cell_[0-9]|lstm_[0-9]/gi; +const regexWeights = /weights|weight|kernel|kernels|w/gi; +const regexFullyConnected = /softmax/gi; + +class CharRNN { + /** + * Create a CharRNN. + * @param {String} modelPath - The path to the trained charRNN model. + * @param {function} callback - Optional. A callback to be called once + * the model has loaded. If no callback is provided, it will return a + * promise that will be resolved once the model has loaded. + */ + constructor(modelPath, callback) { + /** + * Boolean value that specifies if the model has loaded. + * @type {boolean} + * @public + */ + this.ready = false; + + /** + * The pre-trained charRNN model. + * @type {model} + * @public + */ + this.model = {}; + this.cellsAmount = 0; + this.cells = []; + this.zeroState = { c: [], h: [] }; + /** + * The vocabulary size (or total number of possible characters). + * @type {c: Array, h: Array} + * @public + */ + this.state = { c: [], h: [] }; + this.vocab = {}; + this.vocabSize = 0; + this.probabilities = []; + this.defaults = { + seed: "a", // TODO: use no seed by default + length: 20, + temperature: 0.5, + stateful: false, + }; + + this.ready = callCallback(this.loadCheckpoints(modelPath), callback); + // this.then = this.ready.then.bind(this.ready); + } + + resetState() { + this.state = this.zeroState; + } + + setState(state) { + this.state = state; + } + + getState() { + return this.state; + } + + async loadCheckpoints(path) { + const reader = new CheckpointLoader(path); + const vars = await reader.getAllVariables(); + Object.keys(vars).forEach(key => { + if (key.match(regexCell)) { + if (key.match(regexWeights)) { + this.model[`Kernel_${key.match(/[0-9]/)[0]}`] = vars[key]; + this.cellsAmount += 1; + } else { + this.model[`Bias_${key.match(/[0-9]/)[0]}`] = vars[key]; + } + } else if (key.match(regexFullyConnected)) { + if (key.match(regexWeights)) { + this.model.fullyConnectedWeights = vars[key]; + } else { + this.model.fullyConnectedBiases = vars[key]; + } + } else { + this.model[key] = vars[key]; + } + }); + await this.loadVocab(path); + await this.initCells(); + return this; + } + + async loadVocab(path) { + try { + const { data } = await axios.get(`${path}/vocab.json`); + this.vocab = data; + this.vocabSize = Object.keys(data).length; + return this.vocab; + } catch (err) { + return err; + } + } + + async initCells() { + this.cells = []; + this.zeroState = { c: [], h: [] }; + const forgetBias = tf.tensor(1.0); + + const lstm = i => { + const cell = (DATA, C, H) => + tf.basicLSTMCell( + forgetBias, + this.model[`Kernel_${i}`], + this.model[`Bias_${i}`], + DATA, + C, + H, + ); + return cell; + }; + + for (let i = 0; i < this.cellsAmount; i += 1) { + this.zeroState.c.push(tf.zeros([1, this.model[`Bias_${i}`].shape[0] / 4])); + this.zeroState.h.push(tf.zeros([1, this.model[`Bias_${i}`].shape[0] / 4])); + this.cells.push(lstm(i)); + } + + this.state = this.zeroState; + } + + async generateInternal(options) { + await this.ready; + const seed = options.seed || this.defaults.seed; + const length = +options.length || this.defaults.length; + const temperature = +options.temperature || this.defaults.temperature; + const stateful = options.stateful || this.defaults.stateful; + if (!stateful) { + this.state = this.zeroState; + } + + const results = []; + const userInput = Array.from(seed); + const encodedInput = []; + + userInput.forEach(char => { + encodedInput.push(this.vocab[char]); + }); + + let input = encodedInput[0]; + let probabilitiesNormalized = []; // will contain final probabilities (normalized) + + for (let i = 0; i < userInput.length + length + -1; i += 1) { + const onehotBuffer = await tf.buffer([1, this.vocabSize]); + onehotBuffer.set(1.0, 0, input); + const onehot = onehotBuffer.toTensor(); + let output; + if (this.model.embedding) { + const embedded = tf.matMul(onehot, this.model.embedding); + output = tf.multiRNNCell(this.cells, embedded, this.state.c, this.state.h); + } else E{ + output = tf.multiRNNCell(this.cells, onehot, this.state.c, this.state.h); + } + + this.state.c = output[0]; + this.state.h = output[1]; + + const outputH = this.state.h[1]; + const weightedResult = tf.matMul(outputH, this.model.fullyConnectedWeights); + const logits = tf.add(weightedResult, this.model.fullyConnectedBiases); + const divided = tf.div(logits, tf.tensor(temperature)); + const probabilities = tf.exp(divided); + probabilitiesNormalized = await tf.div(probabilities, tf.sum(probabilities)).data(); + + if (i < userInput.length - 1) { + input = encodedInput[i + 1]; + } else { + input = sampleFromDistribution(probabilitiesNormalized); + results.push(input); + } + } + + let generated = ""; + results.forEach(char => { + const mapped = Object.keys(this.vocab).find(key => this.vocab[key] === char); + if (mapped) { + generated += mapped; + } + }); + this.probabilities = probabilitiesNormalized; + return { + sample: generated, + state: this.state, + }; + } + + /** + * Reset the model state. + */ + reset() { + this.state = this.zeroState; + } + + /** + * @typedef {Object} options + * @property {String} seed + * @property {number} length + * @property {number} temperature + */ + + // stateless + /** + * Generates content in a stateless manner, based on some initial text + * (known as a "seed"). Returns a string. + * @param {options} options - An object specifying the input parameters of + * seed, length and temperature. Default length is 20, temperature is 0.5 + * and seed is a random character from the model. The object should look like + * this: + * @param {function} callback - Optional. A function to be called when the model + * has generated content. If no callback is provided, it will return a promise + * that will be resolved once the model has generated new content. + */ + async generate(options, callback) { + this.reset(); + return callCallback(this.generateInternal(options), callback); + } + + // stateful + /** + * Predict the next character based on the model's current state. + * @param {number} temp + * @param {function} callback - Optional. A function to be called when the + * model finished adding the seed. If no callback is provided, it will + * return a promise that will be resolved once the prediction has been generated. + */ + async predict(temp, callback) { + let probabilitiesNormalized = []; + const temperature = temp > 0 ? temp : 0.1; + const outputH = this.state.h[1]; + const weightedResult = tf.matMul(outputH, this.model.fullyConnectedWeights); + const logits = tf.add(weightedResult, this.model.fullyConnectedBiases); + const divided = tf.div(logits, tf.tensor(temperature)); + const probabilities = tf.exp(divided); + probabilitiesNormalized = await tf.div(probabilities, tf.sum(probabilities)).data(); + + const sample = sampleFromDistribution(probabilitiesNormalized); + const result = Object.keys(this.vocab).find(key => this.vocab[key] === sample); + this.probabilities = probabilitiesNormalized; + if (callback) { + callback(result); + } + /* eslint max-len: ["error", { "code": 180 }] */ + const pm = Object.keys(this.vocab).map(c => ({ + char: c, + probability: this.probabilities[this.vocab[c]], + })); + return { + sample: result, + probabilities: pm, + }; + } + + /** + * Feed a string of characters to the model state. + * @param {String} inputSeed - A string to feed the charRNN model state. + * @param {function} callback - Optional. A function to be called when + * the model finished adding the seed. If no callback is provided, it + * will return a promise that will be resolved once seed has been fed. + */ + async feed(inputSeed, callback) { + await this.ready; + const seed = Array.from(inputSeed); + const encodedInput = []; + + seed.forEach(char => { + encodedInput.push(this.vocab[char]); + }); + + let input = encodedInput[0]; + for (let i = 0; i < seed.length; i += 1) { + const onehotBuffer = await tf.buffer([1, this.vocabSize]); + onehotBuffer.set(1.0, 0, input); + const onehot = onehotBuffer.toTensor(); + let output; + if (this.model.embedding) { + const embedded = tf.matMul(onehot, this.model.embedding); + output = tf.multiRNNCell(this.cells, embedded, this.state.c, this.state.h); + } else { + output = tf.multiRNNCell(this.cells, onehot, this.state.c, this.state.h); + } + this.state.c = output[0]; + this.state.h = output[1]; + input = encodedInput[i]; + } + if (callback) { + callback(); + } + } +} + +const charRNN = (modelPath = "./", callback) => new CharRNN(modelPath, callback); + +export default charRNN; + |
DBSCAN is a density-based clustering non-parametric algorithm: given a set of points in some space, it groups together points that are closely packed together (points with many nearby neighbors), + marking as outliers points that lie alone in low-density regions (whose nearest neighbors are too far away). + DBSCAN is one of the most common clustering algorithms and also most cited in scientific literature. +
+