Giter Club home page Giter Club logo

machinelearnjs's Introduction

machinelearn.js

machinelearn.js is a Machine Learning library written in Typescript. It solves Machine Learning problems and teaches users how Machine Learning algorithms work.

Build Status Build status FOSSA Status Slack ZenHub

User Installation

Using yarn

$ yarn add machinelearn

Using NPM

$ npm install --save machinelearn

On the browsers

We use jsdeliver to distribute browser version of machinelearn.js

<script src="https://cdn.jsdelivr.net/npm/machinelearn/machinelearn.min.js"></script>
<script>
    const { RandomForestClassifier } = ml.ensemble;
    const cls = new RandomForestClassifier();
</script>

Please see https://www.jsdelivr.com/package/npm/machinelearn for more details.

Accelerations

By default, machinelearning.js will use pure Javascript version of tfjs. To enable acceleration through C++ binding or GPU, you must import machinelearn-node for C++ or machinelearn-gpu for GPU.

  1. C++
  • installation
yarn add machinelearn-node
  • activation
import 'machinelearn-node';
  1. GPU
  • installation
yarn add machinelearn-gpu
  • activation
import 'machinelearn-gpu';

Highlights

  • Machine Learning on the browser and Node.js
  • Learning APIs for users
  • Low entry barrier

Development

We welcome new contributors of all level of experience. The development guide will be added to assist new contributors to easily join the project.

  • You want to participate in a Machine Learning project, which will boost your Machine Learning skills and knowledge
  • Looking to be part of a growing community
  • You want to learn Machine Learning
  • You like Typescript ❀️ Machine Learning

Simplicity

machinelearn.js provides a simple and consistent set of APIs to interact with the models and algorithms. For example, all models have follow APIs:

  • fit for training
  • predict for inferencing
  • toJSON for saving the model's state
  • fromJSON for loading the model from the checkpoint

Testing

Testing ensures you that you are currently using the most stable version of machinelearn.js

$ npm run test

Supporting

Simply give us a 🌟 by clicking on

Contributing

We simply follow "fork-and-pull" workflow of Github. Please read CONTRIBUTING.md for more detail.

Further notice

Great references that helped building this project!

Contributors

Thanks goes to these wonderful people (emoji key):


Jason Shin

πŸ“ πŸ› πŸ’» πŸ“– ⚠️

Jaivarsan

πŸ’¬ πŸ€” πŸ“’

Oleg Stotsky

πŸ› πŸ’» πŸ“– ⚠️

Ben

πŸ’¬ 🎨 πŸ“’ πŸ› πŸ’»

Christoph Reinbothe

πŸ’» πŸ€” πŸš‡ πŸ‘€

Adam King

πŸ’» ⚠️ πŸ“–

machinelearnjs's People

Contributors

devjiro76 avatar jasonshin avatar lsboss avatar notadamking avatar olegstotsky avatar sudo-ben avatar variablevasasmt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

machinelearnjs's Issues

research/look up scikit learn unit tests

  • I'm submitting a ...
    [/] feature request

  • Summary
    The aim of this exercise is to ensure that Kalimdor is testing the Machine Learning models and utilities correctly by comparing them to ScikitLearn's unit tests. This is a critical step before we expand the codebase even further to avoid any major refactoring in the future.

  • TODOs

  • Check the Scikit-Learn's unit and functional tests against models
  • Check the tests for the utilities such as train_test_split

Project name suggestion

How about chane to "KalchijorimJS".
It is a Korean food name.
Please consider this seriously.

feature/RandomForestRegressor

  • I'm submitting a ...
    [/] feature request

  • Summary
    As we now have abstracted the base operation of RandomForest, we can easily build RandomForestRegressor based on it.

TODO

  • RandomForestRegressor implementation

feature/GradientBoostingClassifier and GradientBoostingRegressor

  • I'm submitting a ...
    [/] feature request

  • Summary
    Gradient Boosting is the most widely used model in Kaggle competitions for both classification and regression problems. It would be nice to introduce boosting algorithms to Kalimdor starting from the standard GradientBoostingClassifier and GradientBoostingRegressor. Benefits of taking this ticket:

  • GB model is no longer a black box to you when you are using it
  • Write declarative code that can be understood by anyone; great for spreading the knowledge
  • Becoming part of the growing open source project
  • Have fun

enhance/decisiont tree classifier max_depth

  • I'm submitting a ...

[/] enhancement

  • Summary

Implement max_depth option to the DecisionTreeClassifier, which is the last stopping criterion from https://www.ibm.com/support/knowledgecenter/en/SS6NHC/com.ibm.swg.im.dashdb.analytics.doc/doc/r_decision_trees_growing.html

The flag is available in the ScikitLearn DecisionTreeClassifier as well, which can be found http://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html#sklearn.tree.DecisionTreeClassifier

Unable to test libsvm-js with Jest (wasm is not support in Jest)

  • I'm submitting a ...
    [/] bug report

  • Summary and Context
    Trying to test code under svm/classes.ts and it initially gives you the following errors:

TypeError: Invalid URL
      
      at new URLImpl (node_modules/whatwg-url/lib/URL-impl.js:21:13)
      at Object.setup (node_modules/whatwg-url/lib/URL.js:376:14)
      at new URL (node_modules/whatwg-url/lib/URL.js:31:9)
      at Object.locateFile (node_modules/libsvm-js/out/wasm/libsvm.js:41:16)
      at integrateWasmJS (node_modules/libsvm-js/out/wasm/libsvm.js:80:15575)
      at SVM (node_modules/libsvm-js/out/wasm/libsvm.js:80:20434)
      at Object.<anonymous> (node_modules/libsvm-js/out/wasm/libsvm.js:94:18)

I realised that Jest is using JSDOM by default as its default mock testEnvironment and it raises an error TypeError: Invalid URL from libsvm-js when it tries to retrieve the current browser URL using getCurrentPathBrowser.

For the testing purpose, I've manually changed the method to return https://www.google.com so it can brute-force satisfy the underlying logic to at least run the wasm code. But the code could not run as per error no native wasm support detected, which means wasm API is not available in JSDOM.

Alternatively, I've tried running the tests with "testEnvironment": "node", which switches Jest's testEnvironment from JSDOM (default) to Node. However, it did not work by throwing the same error as the JSDOM situation.

It will require more investigation...

  • Other information / links

https://jestjs.io/docs/en/configuration#testenvironment-string
https://github.com/mljs/libsvm

enhance/adding a better typing system

  • I'm submitting a ...
    [/] enhancement

  • Summary
    The aim of this ticket is to implement a better typing system throughout the Kalimdor codebase. @BenjaminMcDonald has contributed to this by implementing the initial framework, which we can use as a baseline. Submitted code

import { cloneDeep, isNaN } from 'lodash';
import { exp, mean, pi, pow, sqrt, std } from 'mathjs';
import math from '../utils/MathExtra';

const { isMatrix } = math.contrib;

type TypeMatrix<T> = ReadonlyArray<ReadonlyArray<T>>
interface StrNumDict<T> {
  [key: string]: T;
  [key: number]: T;
}
type StrNumDictArray = StrNumDict<Array<ReadonlyArray<number>>>;


interface InterfaceSummarizeByClass<T> {
  [key: string]: {
    class:T;
    dist: ReadonlyArray<[number, number]>;
  }
} 

/**
 * The Naive is an intuitive method that uses probabilistic of each attribute
 * belonged to each class to make a prediction. It uses Gaussian function to estimate
 * probability of a given class.
 *
 * @example
 * import { GaussianNB } from 'kalimdor/naive_bayes';
 *
 * const nb = new GaussianNB();
 * const X = [[1, 20], [2, 21], [3, 22], [4, 22]];
 * const y = [1, 0, 1, 0];
 * nb.fit({ X, y });
 * nb.predict({ X: [[1, 20]] }); // returns [ 1 ]
 *
 */
export class GaussianNB<T extends string | number = number> {
  /**
   * Naive Bayes summary according to classes
   */
  private summaries: InterfaceSummarizeByClass<T> = null;

  /**
   * @param clone - To clone the input values during fit and predict
   */
  constructor(private clone:boolean = true) {}

  /**
   * Fit date to build Gaussian Distribution summary
   * @param {T} X - training values
   * @param {T} y - target values
   */
  public fit(
    {
      X = null,
      y = null
    }: {
      X: TypeMatrix<number>;
      y: ReadonlyArray<T>;
    } = {
      X: null,
      y: null
    }
  ): void {
    if (!isMatrix(X)) {
      throw new Error('X must be a matrix');
    }
    if (!Array.isArray(y)) {
      throw new Error('y must be a vector');
    }
    if (X.length !== y.length) {
      throw new Error('X and y must be same in length');
    }
    let clonedX = X;
    let clonedY = y;
    if (this.clone) {
      clonedX = cloneDeep(X);
      clonedY = cloneDeep(y);
    }
    this.summaries = this.summarizeByClass(clonedX, clonedY);
  }

  /**
   * Predict multiple rows
   * @param {T[]} X - values to predict in Matrix format
   * @returns {number[]}
   */
  public predict(
    {
      X = null
    }: {
      X: TypeMatrix<number>;
    } = {
      X: null
    }
  ): T[] {
    if (!isMatrix(X)) {
      throw new Error('X must be a matrix');
    }
    // let clonedX = X;

    // if (this.clone) {
    //   clonedX = cloneDeep(X);
    // }
    // const result:T[] = [];
    // for (let i = 0; i < clonedX.length; i++) {
    //   result.push(this.singlePredict(clonedX[i]));
    // }
    return X.map(x => this.singlePredict(x));
  }

  /**
   * Restores GaussianNB model from a checkpoint
   * @param summaries - Gaussian Distribution summaries
   */
  public fromJSON(
    {
      summaries = null
    }: {
      summaries: {};
    } = {
      summaries: null
    }
  ): void {
    this.summaries = summaries;
  }

  /**
   * Returns a model checkpoint
   */
  public toJSON(): {
    summaries: {};
  } {
    return {
      summaries: this.summaries
    };
  }

  /**
   * Make a prediction
   * @param X -
   */
  private singlePredict(X:ReadonlyArray<number>): T {
    const summaryKeys:ReadonlyArray<string> = Object.keys(this.summaries);

    // Comparing input and summary shapes
    const summaryLength = this.summaries[summaryKeys[0]].dist.length;
    const inputLength = X.length;
    if (inputLength > summaryLength) {
      throw new Error('Prediction input X length must be equal or less than summary length');
    }

    // Getting probability of each class
    // TODO Log Probabilities
    const probabilities:StrNumDict<number> = {};
    for (const key of summaryKeys) {
      probabilities[key] = 1;
      const classSummary:ReadonlyArray<[number, number]> = this.summaries[key].dist;
      for (let j = 0; j < classSummary.length; j++) {
        const [meanval, stdev] = classSummary[j];
        const probability:number = this.calculateProbability(X[j], meanval, stdev);
        if (!isNaN(probability)) {
          probabilities[key] *= probability;
        }
      }
    }


    // // Vote the best predction
    const [keyOfBestClass, probOfBestClass] = Object.entries(probabilities)
        .reduce((maxEntry, [key, prob]) => maxEntry && maxEntry[1] > prob ? maxEntry:[key, prob]);

    // Calculate Class Probabilities
    // const totalProbs = Object.values(probabilities)
    //     .reduce((sum, prob) => sum + prob, 0);
    // const classProbability = probOfBestClass / totalProbs;

    return this.summaries[keyOfBestClass].class;
  }

  /**
   * Calculate the main division
   * @param x
   * @param meanval
   * @param stdev
   */
  private calculateProbability(x:number, meanval:number, stdev:number): number {
    const stdevPow:any = pow(stdev, 2);
    const meanValPow = -pow(x - meanval, 2);
    const exponent = exp(meanValPow / (2 * stdevPow));
    return (1 / (sqrt(pi.valueOf() * 2) * stdev)) * exponent;
  }

  /**
   * Summarise the dataset per class using "probability density function"
   * example:
   * Given
   * const X = [[1,20], [2,21], [3,22], [4,22]];
   * const y = [1, 0, 1, 0];
   * Returns
   * { '0': [ [ 3, 1.4142135623730951 ], [ 21.5, 0.7071067811865476 ] ],
   * '1': [ [ 2, 1.4142135623730951 ], [ 21, 1.4142135623730951 ] ] }
   * @param dataset
   */
  private summarizeByClass(X:TypeMatrix<number>, y:ReadonlyArray<T>): InterfaceSummarizeByClass<T> {
    const separated:StrNumDictArray = this.separateByClass(X, y);
    const summarize: InterfaceSummarizeByClass<T> = {};
    for (const key of Object.keys(separated)) {
      // Finding the real target value from y array
      const targetClass:T = y.find(z => z.toString() === key);
      // Mutating "separated" variable instead of immutable approach for performance
      separated[key].forEach(x => x.push(targetClass));
      const dataset = separated[key];
      // storing object to each attribute to store real class value and dist summary
      summarize[key] = {
        class: targetClass,
        dist: this.summarize(dataset)
      };
    }
    return summarize;
  }

  /**
   * Summarise the dataset to calculate the β€˜pdf’ (probability density function) later on
   * @param dataset
   */
  private summarize(dataset:Array<Array<string|number>>): ReadonlyArray<[number, number]> {
    const sorted = [];
    // Manual ZIP; simulating Python's zip(*data)
    // TODO: Find a way to use a built in function
    for (let zRow = 0; zRow < dataset.length; zRow++) {
      const row = dataset[zRow];
      for (let zCol = 0; zCol < row.length; zCol++) {
        // Pushes a new array placeholder if it's not populated yet at zRow index
        if (typeof sorted[zCol] === 'undefined') {
          sorted.push([]);
        }
        const element = dataset[zRow][zCol];
        sorted[zCol].push(element);
      }
    }

    const summaries = [];
    for (let i = 0; i < sorted.length; i++) {
      const attributes: any = sorted[i];
      summaries.push([mean(attributes), std(attributes)]);
    }
    // Removing the last element
    summaries.pop();
    return summaries;
  }

  /**
   * Separates X by classes specified by y argument
   * Given
   * const X = [[1,20], [2,21], [3,22], [4,22]];
   * const y = [1, 0, 1, 0];
   * Returns
   * { '0': [ [2,21], [4,22] ],
   * '1': [ [1,20], [3,22] ] }
   * @param X
   * @param y
   */
  private separateByClass(X:TypeMatrix<number>, y:ReadonlyArray<T>):StrNumDictArray {
    const result:StrNumDictArray = {};
    for (let i = 0; i < X.length; i++) {
      const row:ReadonlyArray<number> = X[i];
      const target:string|number = y[i];
      if (result[target]) {
        // If value already exist
        result[target].push(row);
      } else {
        result[target] = [];
        result[target].push(row);
      }
    }
    return result;
  }
}

in gaussian.ts

TODO

  • Fix the compilation error occurring at line 212
  • Gradually implement the new typing system into the codebase
  • Do a research on "type guard"

Plan of Attack: Moving the project to a Github organisation

As discussed, we will be moving this project to a Github organisation. There are few tasks that need immediate attention upon migration to an organisation.

TODOs:

  1. Try moving a dummy project to an organisation and see how the original link changes; does it redirect or break?
  2. Make sure the current Kalimdor Travis project is still connected, otherwise, reconnect it and update the badge
  3. The original repository link should redirect to the new repository in the organisation once it is tranferred over

Enhance RandomForest

  • I'm submitting a ...
    [/] feature request

  • Summary
    As a follow up task of "Enhance Decision Tree", we can now make a better RandomForest algorithm based on it.

  • Remove all Lodash based loops
  • test with large data
  • Fix the bagging prediction

feature/β€ŠLinear Discriminant Analysis

  • I'm submitting a ...
    [/] feature request

  • Summary

A classifier with a linear decision boundary, generated by fitting class conditional densities to the data and using Bayes’ rule.

The model fits a Gaussian density to each class, assuming that all classes share the same covariance matrix.

The fitted model can also be used to reduce the dimensionality of the input by projecting it to the most discriminative directions.

docs/UX enhancement

  • I'm submitting a ...
    [/] enhancement
    [/] docs

  • Summary

Throwing in some UX improvement ideas for the documentation site.

  1. Using more images to describe the features. Since Kalimdor is a Machine Learning library, which is data heavy, use graphs as much as possible to explain the internals of the models.

  2. More paddings between logo <-> CTA <-> subheadings

feature/normalize l2

  • I'm submitting a ...
    [/] feature request

  • Summary
    This ticket aims to implement normalize API that can be used for a wide variety of regression models.

Equation:
X_normalized = X - min(X)/(max(X) - min(X))

The process of L2 normalization

Given an example

>>> X = [[ 1., -1.,  2.],
...      [ 2.,  0.,  0.],
...      [ 0.,  1., -1.]]
>>> X_normalized = preprocessing.normalize(X, norm='l2')

>>> X_normalized                                      
array([[ 0.40..., -0.40...,  0.81...],
       [ 1.  ...,  0.  ...,  0.  ...],
       [ 0.  ...,  0.70..., -0.70...]])
  1. Take all the X-axis then √1^2 + -1^2 + 2^2 = √6
  2. Divide each number of the X-axis with √6 and reconstruct the matrix

References:
http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.normalize.html

feature/chi2

  • I'm submitting a ...
    [/] feature request

  • Summary
    Implement chi2 feature selection

enhance/MinMaxScaler

  • I'm submitting a ...
    [/] enhancement

  • Summary

MinMaxScaler should also accept Matrix as input.

Example:

const data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]];
.....
scaler.fit(data); 

feature/Diabetes toy dataset

  • I'm submitting a ...
    [/] feature request

  • Summary

Following up with the implementation of datasets/boston and datasets/iris, it would be nice to introduce one more toy dataset https://archive.ics.uci.edu/ml/datasets/diabetes. This ticket would be a great first ticket for anyone who wants to try out a feature implementation for the first time in the Kalimdor project.

feature/Multivariate Linear Regression - extending the existing linear regresson

  • I'm submitting a ...
    [/] feature request

  • Summary

In the linear regression API of Kalimdor, it is only supporting a single variable. It would be ideal to extend it to support multivariate linear regression as well.

TODO:

  • Implementation
  • Testing
  • Documentation update
  • Add example usage for both single variable and multivariate

Reference: https://datascience.stackexchange.com/questions/8625/multivariate-linear-regression-in-python

Enhance dataset APIs

  • I'm submitting a ...
    [/] enhancement

  • Summary

  • Update the existing API to be more immutable
  • Add Diabetes dataset
  • Add Boston House price dataset

refactor/generic input validation

  • I'm submitting a ...
    [/] refactor

  • Summary
    We should come up with an architecture to deal with input validations more generically. At the moment in Kalimdor, input validations are done inconsistently and error messages are manually written on each function. This is not a scalable solution and should be addressed soon before the code base becomes bigger. This issue aims to build an abstraction of the validation logic that can be repeated and applied globally without too much effort.

Ideally, we should:

  • Find the most Typescript way of achieving this

    • We are already validating the input type using function types, but it stops working once you compile the code into ES6
  • If not, come up with an architecture to handle this problem

Write tests for preprocessing

  • I'm submitting a ...
    [ ] bug report
    [ ] feature request
    [ ] question about the decisions made in the repository
    [ ] question about how to use this project

  • Summary

  • Other information (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.)

k_means import issue; cannot import it as suggested in the example code on the website

  • I'm submitting a ...
    [/] bug report

  • Summary

The example code on the website suggests

import { KMeans } from 'kalimdor/k_means';

const kmean = new KMeans({ k: 2 });
const clusters = kmean.fit({ X: [[1, 2], [1, 4], [1, 0], [4, 2], [4, 4], [4, 0]] });

const result = kmean.predict({ X: [[0, 0], [4, 4]] });
// results in: [0, 1]

but import { KMeans } from 'kalimdor/k_means'; is incorrect, it's should be import { KMeans } from 'cluster/k_means';

Enhance DecisionTree

  • I'm submitting a ...
    [/] feature request

  • Summary

  • Make sure the documentation is right
  • Test against a large dataset
  • Other information (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.)

enhance/Remove or Disable Codacy checks

  • I'm submitting a ...
    [/] Enhancement

  • Summary
    I don't think we are utilising Codacy at all, so it would be easier to simply disable it for now. I was thinking to use it for code coverage report but Codacy supports more features than the coverage reporting, and it causes the pull request check failure for reasons outside of our standard (e.g. additional linting).

Benchmark performance

  • I'm submitting a ...
    [/] performance improvement
    [/] research

  • **Summary and todos **

  1. Kalimdor uses recursive loops, such as .reduce or .map throughout the codebase. Should we favour plain for loops instead? Research and experiment are required..

Investigate using Appveyor for CI instead of Travis

  • I'm submitting a ...

[/] enhancement

  • Summary

Due to the demand for supporting Windows for the development and the production environment, it would be nice to have a Windows CI environment, but it is not supported by Travis CI. After conducting an initial investigation, Appveyor stands out as an alternative to Travis since it is free for open source and provides both Windows and Linux build environments. The ticket aims to do a further investigation and possibly make a pull request with Appveyor CI config file.

Documentation improvements

  • I'm submitting a ...
    [/] feature request

  • Summary

Improvements to make to the current documentation site.

  • Method orders: Ideally, orders should be
  1. fit
  2. predict
  3. toJSON
  4. fromJSON
  • toJSON method definition is not showing up

  • When a method return type is an object, it should display a table

Add Naive Bayes Classifier

  • I'm submitting a ...
    [/] feature request

  • Summary
    Add Naive Bayes Classifier!

Two types will be required

  • Other information (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.)

feature/CDN js

  • I'm submitting a ...
    [/] feature request

  • Summary
    Upon each release, we should also make the release available through CDN.js or a similar service. It will allow users without an NPM environment to easily drop in Kalimdor and start using. CNDjs script is also required to build JSFiddle demos.

Training a model should progressively log its progress

  • I'm submitting a ...
    [/] feature request

  • Summary
    I'm thinking we can expose something like

const clusters = kmean.fit({
   X: [[1, 2], [1, 4], [1, 0], [4, 2], [4, 4], [4, 0]],
   log: function(msg) {
     // msg contains the details about the training progress
   }
 });

Considerations:

  • Enable it optionally

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.