Selasa, 03 Desember 2024

SPACE INVADERS JAVA






 /*

 * To change this license header, choose License Headers in Project Properties.

 * To change this template file, choose Tools | Templates

 * and open the template in the editor.

 */

package spaceinvadergame;

import javax.swing.*;

import java.awt.*;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import java.awt.event.KeyEvent;

import java.awt.event.KeyListener;

import java.util.ArrayList;

import java.util.Iterator;

import java.util.Random;


public class SpaceInvaderGame extends JPanel implements ActionListener, KeyListener {

    private Timer timer;

    private int spaceshipX;

    private ArrayList<Rectangle> bullets;

    private ArrayList<Rectangle> enemies;

    private Random random;


    public SpaceInvaderGame() {

        timer = new Timer(10, this);

        timer.start();

        spaceshipX = 250;

        bullets = new ArrayList<>();

        enemies = new ArrayList<>();

        random = new Random();

        this.setFocusable(true);

        this.addKeyListener(this);

        generateEnemies();

    }


    private void generateEnemies() {

        for (int i = 0; i < 5; i++) {

            enemies.add(new Rectangle(50 + i * 80, 50, 40, 40));

        }

    }


    @Override

    protected void paintComponent(Graphics g) {

        super.paintComponent(g);

        g.setColor(Color.BLUE);

        g.fillRect(spaceshipX, 500, 50, 50);


        g.setColor(Color.RED);

        for (Rectangle bullet : bullets) {

            g.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);

        }


        g.setColor(Color.GREEN);

        for (Rectangle enemy : enemies) {

            g.fillRect(enemy.x, enemy.y, enemy.width, enemy.height);

        }

    }


    @Override

    public void actionPerformed(ActionEvent e) {

        Iterator<Rectangle> bulletIterator = bullets.iterator();

        while (bulletIterator.hasNext()) {

            Rectangle bullet = bulletIterator.next();

            bullet.y -= 5;

            if (bullet.y < 0) {

                bulletIterator.remove();

            } else {

                for (Iterator<Rectangle> enemyIterator = enemies.iterator(); enemyIterator.hasNext();) {

                    Rectangle enemy = enemyIterator.next();

                    if (bullet.intersects(enemy)) {

                        bulletIterator.remove();

                        enemyIterator.remove();

                        break;

                    }

                }

            }

        }


        repaint();

    }


    @Override

    public void keyPressed(KeyEvent e) {

        int key = e.getKeyCode();

        if (key == KeyEvent.VK_LEFT && spaceshipX > 0) {

            spaceshipX -= 5;

        } else if (key == KeyEvent.VK_RIGHT && spaceshipX < getWidth() - 50) {

            spaceshipX += 5;

        } else if (key == KeyEvent.VK_SPACE) {

            bullets.add(new Rectangle(spaceshipX + 20, 500, 10, 20));

        }

    }


    @Override

    public void keyReleased(KeyEvent e) {}


    @Override

    public void keyTyped(KeyEvent e) {}


    public static void main(String[] args) {

        JFrame frame = new JFrame("Space Invader Game");

        SpaceInvaderGame game = new SpaceInvaderGame();

        frame.add(game);

        frame.setSize(600, 600);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        frame.setVisible(true);

    }

}


Senin, 02 Desember 2024

Java gui animation 6 circles rotate code




Creating an animation with six rotating circles in Java can be a fun challenge! Here's an example using Java Swing that animates six circles rotating around a central point:

java
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class RotatingCirclesAnimation extends JPanel implements ActionListener {
    private Timer timer;
    private double angle = 0;
    private int radius = 100;
    private int numCircles = 6;
    private int circleDiameter = 50;

    public RotatingCirclesAnimation() {
        timer = new Timer(16, this); // Update every 16 milliseconds (60 FPS)
        timer.start();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;

        for (int i = 0; i < numCircles; i++) {
            double theta = angle + (2 * Math.PI * i / numCircles);
            int x = (int) (centerX + radius * Math.cos(theta)) - circleDiameter / 2;
            int y = (int) (centerY + radius * Math.sin(theta)) - circleDiameter / 2;
            g2d.setColor(Color.getHSBColor((float) i / numCircles, 1.0f, 1.0f));
            g2d.fillOval(x, y, circleDiameter, circleDiameter);
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        angle += Math.PI / 180; // Increment angle to rotate circles
        repaint();
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("Rotating Circles Animation");
        RotatingCirclesAnimation animation = new RotatingCirclesAnimation();
        frame.add(animation);
        frame.setSize(800, 600);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}

In this code:

  • The RotatingCirclesAnimation class extends JPanel and implements ActionListener.

  • A Timer is used to update the angle every 16 milliseconds, creating a smooth 60 FPS animation.

  • The paintComponent method calculates the positions of six circles based on the angle and draws them.

  • The actionPerformed method updates the angle to rotate the circles and repaints the panel.

  • The main method sets up the JFrame and adds the animation panel to it.

Feel free to experiment and customize the code to suit your needs. Enjoy creating your animation! If you have any more questions or need further assistance, I'm here to help.

Javascript Function2

 javascript

// Function to add two numbers
function addNumbers(a, b) {
  return a + b;
}

// Calling the function
let sum = addNumbers(5, 3);
console.log(sum); // Output: 8

Function


 

Timing control for script-based animations W3C Working Draft 21 February 2012

 

1. Introduction

This section is informative.

Animations in web browsers come in two forms: native, declarative ones, such as the <animate> element in SVG, and those that are implemented in script. These script-based animations are most often performed by scheduling a callback using setTimeout or setInterval and making changes to the DOM to effect the animation in that callback.

A disadvantage of this approach is that the author of the animation script has no idea what the ideal frequency for updating their animation is. Instead, the easiest way forward for the author is to simply call setTimeout with a very small value, which in practice will be clamped to some minimum time like 10ms anyway. It likely won’t be the case that 100 updates per second are required for the animation, especially if the page is in a background tab or the browser window is minimized.

The API described in this document allows script authors to request the user agent schedule an animation frame update. The user agent is in a better position to determine how many frames per second to allocate to all of the animations running in the entire browser. If there are many animations active, the user agent can select a frame rate such that all of the animations will run as smoothly as possible. If the page is not currently visible, animations on that page can be throttled heavily so that they do not update often and thus consume little CPU power.

Example

Here is an example of using the API to write a script-based animation.

HTML
<!DOCTYPE html>
<title>Script-based animation using requestAnimationFrame</title>
<style>
div { position: absolute; left: 10px; padding: 50px;
  background: crimson; color: white }
</style>
<script>
var requestId = 0;

function animate(time) {
  document.getElementById("animated").style.left =
    (time - animationStartTime) % 2000 / 4 + "px";
  requestId = window.requestAnimationFrame(animate);
}
function start() {
  animationStartTime = Date.now();
  requestId = window.requestAnimationFrame(animate);
}
function stop() {
  if (requestId)
    window.cancelAnimationFrame(requestId);
  requestId = 0;
}
</script>
<button onclick="start()">Click me to start!</button>
<button onclick="stop()">Click me to stop!</button>
<div id="animated">Hello there.</div>

2. Conformance

Everything in this specification is normative except for diagrams, examples, notes and sections marked as being informative.

The keywords “must”, “must not”, “required”, “shall”, “shall not”, “should”, “should not”, “recommended”, “may” and “optional” in this document are to be interpreted as described in Key words for use in RFCs to Indicate Requirement Levels. [RFC2119]

The IDL fragment in section 4 of this specification must be interpreted as required for conforming IDL fragments, as described in the Web IDL specification. [WEBIDL]

This specification defines a single conformance class:

conforming user agent
A user agent is considered to be a conforming user agent if it satisfies all of the must-, required- and shall-level criteria in this specification. A conforming user agent must also be a conforming implementation of the IDL fragment in section 4 of this specification, as described in the Web IDL specification. [WEBIDL]

This specification references interfaces and types from a number of other specifications:

3. Definitions

Associated with every Document is an animation frame request callback list, which is a list of <handlecallback> tuples. handle is an integer that uniquely identifies the entry in the list. callback is a FrameRequestCallback object. Initially, the animation frame request callback list for a Document is empty.

Document is said to have active animations whenever it has a non-empty animation frame request callback list.

4. The WindowAnimationTiming interface

The WindowAnimationTiming interface is used to expose the requestAnimationFrame operation on the Window object. In the definition of requestAnimationFrame below, references to the Document object are to be taken to be references to the Window object’s active document[HTML5]

[NoInterfaceObject]
interface WindowAnimationTiming {
  long requestAnimationFrame(FrameRequestCallback callback);
  void cancelAnimationFrame(long handle);
};

Window implements WindowAnimationTiming;

callback FrameRequestCallback = void (DOMTimeStamp time);

The requestAnimationFrame method is used to signal to the user agent that a script-based animation needs to be resampled. When requestAnimationFrame(callback) is called, the user agent must schedule a script-based animation resampling by appending to the end of the animation frame request callback list an entry whose handle is a user-agent-defined integer greater than zero that uniquely identifies the entry in the list and whose callback is callback.

Each FrameRequestCallback object has a cancelled boolean flag. This flag is initially false and is not exposed by any interface.

Note

requestAnimationFrame only schedules a single update to the script-based animation. If subsequent animation frames are needed, then requestAnimationFrame will need to be called again from within the callback.

Also note that multiple calls to requestAnimationFrame with the same callback (before callbacks are invoked and the list is cleared) will result in multiple entries being in the list with that same callback, and thus will result in that callback being invoked more than once for the animation frame.

Editorial note

ISSUE-4 Do we want to allow an Element to be passed to requestAnimationFrame, so that animations affecting the given element are throttled or paused when scrolled out of view?

The cancelAnimationFrame method is used to cancel a previously made request to schedule an animation frame update. When cancelAnimationFrame(handle) is called, the user agent must set the cancelled flag to true for the callback registered on this Document whose handle is handle. The cancelled flag is set whether the callback is in a animation frame request callback list or not. If there is no callback with the given handle, then this function does nothing.

Note

cancelAnimationFrame might be called for an entry in the Document’s animation frame request callback list or in the sample all animations operation’s temporary list. In either case the entry’s cancelled flag is set to true so that the callback does not run.

5. Processing Model

Whenever a Document's hidden attribute is false and the animation frame request callback list is not empty, the user agent MUST regularly queue a task that samples all animations for that Document's top-level browsing context. The task source for these tasks is the animation task source. Only one task should be generated per top-level browsing context, even if multiple Documents within the same top-level browsing context are not hidden and contain callbacks. To samples all animations , the following steps are performed:
  1. Let t be the result of getting the next sample time of the top-level browsing context.
    Editorial note

    ISSUE-2 Getting the next sample time is currently undefined. This time should be the same as the time that contemporaneous native animations have been sampled at.

  2. Let time be t expressed as the number of milliseconds since 1970-01-01T00:00:00Z.
    Editorial note

    ISSUE-3 Having animation frame times run off a monotonic clock that increases at a constant rate would be better for authors than using the wallclock time as reported by the system, which might jump backwards or move forwards at varying rates due to clock slew. Doing this argues for the reinclusion of the Window.animationStartTime attribute that was present in an earlier draft, so that scripts can avoid using Date.now() to record the animation start time, which might bear little relation to the monotonic clock values anyway.

  3. Let list be an empty animation frame request callback list.
  4. Let contexts be the results of list of the descendant browsing contexts algorithm for this task's top-level browsing context.
  5. For every context in contexts, in any order, perform the following steps:
    1. Let d be context's active document.
    2. If d's hidden attribute is true, continue to the next entry in the contexts list. Otherwise proceed with these steps.
    3. Let doclist be d's animation frame request callback list.
    4. Append all entries from doclist into list preserving order.
    5. Clear doclist.
  6. Perform the steps defined in the invoke callbacks algorithm with parameters list and time

The invoke callbacks algorithm:

  1. For each entry callback in list, in order:
    1. If the cancelled flag on callback is not true:
      1. Call callback with time as the argument.
      2. If calling the operation resulted in an exception being thrown, then catch that exception and ignore it.
Note

The expectation is that the user agent will run tasks from the animation task source at at a regular interval matching the display's refresh rate. Running tasks at a lower rate can result in animations not appearing smooth. Running tasks at a higher rate can cause extra computation to occur without a user-visible benefit.

6. Acknowledgements

This section is informative.

The editors would like to thank the following people for contributing to this specification: Boris Zbarsky, Jonas Sicking, Robert O’Callahan.

A. References

A.1. Normative references

[IETF RFC 2119]
Key words for use in RFCs to Indicate Requirement Levels, Scott Bradner, Author. Internet Engineering Task Force, March 1997. Available at http://www.ietf.org/rfc/rfc2119.txt.
[DOM Level 3 Core]
Document Object Model Level 3 Core Specification, A. Le Hors, et al., Editors. World Wide Web Consortium, 7 April 2004. This version of the Document Object Model Level 3 Core Recommendation is http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407. The latest version of DOM Core is available at http://www.w3.org/TR/domcore/.
[HTML5]
HTML5, Ian Hickson, Editor. World Wide Web Consortium, May 2011. This version of the HTML5 is available from http://www.w3.org/TR/html5/. The latest editor's draft is available at http://dev.w3.org/html5/spec/.
[Web IDL]
Web IDL, Cameron McCormack, Editor. World Wide Web Consortium, February 2012. This version of the Web IDL specification is available from http://www.w3.org/TR/2012/WD-WebIDL-20120207/. The latest version of Web IDL is available at http://www.w3.org/TR/WebIDL/.

A.2. Informative references

Minggu, 01 Desember 2024

Mixture Model Clustering Using C#

 

Mixture Model Clustering Using C#

By James McCaffrey

James McCaffreyData clustering is the process of grouping data items so that similar items are in the same group/cluster and dissimilar items are in different clusters. The most commonly used clustering algorithm is called k-means. The k-means approach is simple and effective, but it doesn’t always work well with a dataset that has skewed distributions.

In this article I explain how to implement mixture model data clustering using the C# language. The best way to understand what mixture model clustering is and to see where this article is headed is to examine the demo program in Figure 1. The demo sets up a tiny dummy dataset with eight items. Each data item represents the height and width of a package of some sort. The first item is (0.2000, 0.7000) and the last item is (0.7000, 0.1000).

Mixture Model Clustering Demo Run
Figure 1 Mixture Model Clustering Demo Run

There are many variations of mixture model clustering. The variation presented in this article works only with numeric data and requires that the data be normalized or scaled so that all values are roughly in the same range. Normalization prevents large values, such as annual incomes, from overwhelming small values such as high school GPAs.

The demo program specifies the number of clusters as K = 3. Most clustering techniques, including mixture model clustering, require you to set the number of clusters, and this must be determined by trial and error. Mixture model clustering uses a technique called expectation-maximization (EM) optimization, which is an iterative process. The demo iterates five times.

The final clustering result is contained in a matrix called membership weights, w. The w matrix has eight rows. Each row of w corresponds to a row of the dataset.

The first row of w is (0.9207, 0.0793, 0.0000). Notice that the values in each row of w sum to 1.0 so they can be loosely interpreted as the probability that the corresponding data item belongs to a cluster. Therefore, data item [0] belongs to cluster 0 because column [0] of w has the largest value. Similarly, the value of w[7] is (0.0000, 0.2750, 0.7250), so data item [7] belongs to cluster 2.

The demo program displays the values of internal data structures named Nk (column sums of w), a (mixture weights), u (cluster means) and V (cluster variances). These values are used by the EM optimization algorithm part of mixture model clustering.  

This article assumes you have intermediate or better programming skills with C#, but doesn’t assume you know anything about mixture model data clustering. The demo is coded using C#, but you shouldn’t have any trouble refactoring the code to another language such as JavaScript or Visual Basic. The complete demo code is presented in this article. The source code is also available in the accompanying download.

Understanding the Multivariate Gaussian Distribution

Mixture model clustering and  EM are really general techniques rather than specific algorithms. The key math equations for the variation of mixture model clustering and EM optimization used in this article are shown in Figure 2. Depending on your math background, the equations may appear intimidating, but they’re actually quite simple, as you’ll see shortly.

Mixture Model Expectation-Maximization Optimization Equations
Figure 2 Mixture Model Expectation-Maximization Optimization Equations

The key to the variation of mixture model clustering presented here is understanding the multivariate Gaussian (also called normal) probability distribution. And to understand the multivariate Gaussian distribution, you must understand the univariate Gaussian distribution.

Suppose you have a dataset that consists of the heights (in inches) of men who work at a large tech company. The data would look like 70.5, 67.8, 71.4 and so forth (all inches). If you made a bar graph for the frequency of the heights, you’d likely get a set of bars where the highest bar is for heights between 68.0 to 72.0 inches (frequency about 0.6500). You’d get slightly lower bars for heights between 64.0 to 68.0 inches, and for 72.0 inches to 76.0 inches (about 0.1400 each), and so on. The total area of the bars would be 1.0. Now if you drew a smooth line connecting the tops of the bars, you’d see a bell-shaped curve.

The univariate Gaussian distribution is a mathematical abstrac­tion of this idea. The exact shape of the bell-shaped curve is determined by the mean (average) and variance (measure of spread). The math equation that defines the bell-shaped curve of a univariate Gaussian distribution is called the probability density function (PDF). The math equation for the PDF of a univariate Gaussian distribution is shown as equation (1) in Figure 2. The variance is given by sigma squared. For example, if x = 1.5, u = 0, and var = 1.0 then f(x, u, var) = 0.1295, which is the height of the bell-shaped curve at point x = 1.5.

The value of the PDF for a given value of x isn’t a probability. The probability of a range of values for x is the area under the curve of the PDF function, which can be determined using calculus. Even though a PDF value by itself isn’t a probability, you can compare PDF values to get relative likelihoods. For example, if PDF(x1) = 0.5000 and PDF(x2) = 0.4000 you can conclude that the probability of x1 is greater than the probability of x2. 

Now, suppose your dataset has items where each has two or more values. For example, instead of just the heights of men, you have (height, weight, age). The math abstraction of this scenario is called the multivariate Gaussian distribution. The dimension, d, of a multivariate Gaussian distribution is the number of values in each data item, so (height, weight, age) data has dimension d = 3.

The equation for the PDF of a multivariate Gaussian distribution is shown as equation (2) in Figure 2. The x is in bold to indicate it’s a multivalued vector. For a multivariate Gaussian distribution, instead of a variance, there is a (d x d) shape covariance matrix, represented by Greek letter uppercase sigma. Calculating the PDF for a univariate x is simple, but calculating the PDF for a multivariate x is extremely difficult because you must calculate the determinant and inverse of a covariance matrix.

Instead of using the multivariate Gaussian distribution, you can greatly simplify mixture model calculations by assuming that each component of a multivalued data item follows an independent univariate Gaussian distribution. For example, suppose d = 3 and x = (0.4, 0.6, 0.8). Instead of calculating a multivalued mean and a (d x d) multivalued covariance matrix and then using equation (2), you can look at each data component separately. This would give you three means, each a single value, and three variances, each also a single value. Then you can calculate three separate PDF values using equation (1) and average the three PDF values to get a final PDF value.

This technique is called a Gaussian mixture model because it assumes the data is clustered according to a mixture of K multivariate Gaussian probability distributions. It’s known as a naive approach because the assumption that the data components are mathematically independent of each other is rarely true for real-life data. However, the naive Gaussian PDF approach is dramatically simpler than calculating a true multivariate Gaussian PDF, and the naive technique often (but not always) works well in practice. 

The Demo Program

The complete demo program, with several minor edits to save space, is presented in Figure 3. To create the program, I launched Visual Studio and created a new console application named MixtureModel. I used Visual Studio 2017, but the demo has no significant .NET Framework dependencies.

Figure 3 The Mixture Model Clustering Demo Program

C#
using System;
namespace MixtureModel
{
  class MixtureModelProgram
  {
    const int N = 8; const int K = 3; const int d = 2;
    static void Main(string[] args)
    {
      Console.WriteLine("Begin mixture model with demo ");
      double[][] x = new double[N][];  // 8 x 2 data
      x[0] = new double[] { 0.2, 0.7 };
      x[1] = new double[] { 0.1, 0.9 };
      x[2] = new double[] { 0.2, 0.8 };
      x[3] = new double[] { 0.4, 0.5 };
      x[4] = new double[] { 0.5, 0.4 };
      x[5] = new double[] { 0.9, 0.3 };
      x[6] = new double[] { 0.8, 0.2 };
      x[7] = new double[] { 0.7, 0.1 };
      Console.WriteLine("Data (height, width): ");
      Console.Write("[0] "); VectorShow(x[0]);
      Console.Write("[1] "); VectorShow(x[1]);
      Console.WriteLine(". . . ");
      Console.Write("[7] "); VectorShow(x[7]);
      Console.WriteLine("K=3, initing w, a, u, S, Nk");
      double[][] w = MatrixCreate(N, K);
      double[] a = new double[K] { 1.0/K, 1.0/K, 1.0/K };
      double[][] u = MatrixCreate(K, d);
      double[][] V = MatrixCreate(K, d, 0.01);
      double[] Nk = new double[K];
      u[0][0] = 0.2; u[0][1] = 0.7;
      u[1][0] = 0.5; u[1][1] = 0.5;
      u[2][0] = 0.8; u[2][1] = 0.2;
      Console.WriteLine("Performing 5 E-M iterations ");
      for (int iter = 0; iter < 5; ++iter) {
        UpdateMembershipWts(w, x, u, V, a);  // E step
        UpdateNk(Nk, w);  // M steps
        UpdateMixtureWts(a, Nk);
        UpdateMeans(u, w, x, Nk);
        UpdateVariances(V, u, w, x, Nk);
      }
      Console.WriteLine("Clustering done. \n");
      ShowDataStructures(w, Nk, a, u, V);
      Console.WriteLine("End mixture model demo");
      Console.ReadLine();
    } // Main
    static void ShowDataStructures(double[][] w,
      double[] Nk, double[] a, double[][] u, double[][] V)
    {
      Console.WriteLine("w:"); MatrixShow(w, true);
      Console.WriteLine("Nk:"); VectorShow(Nk, true);
      Console.WriteLine("a:"); VectorShow(a, true);
      Console.WriteLine("u:"); MatrixShow(u, true);
      Console.WriteLine("V:"); MatrixShow(V, true);
    }
    static void UpdateMembershipWts(double[][] w,
      double[][] x, double[][] u, double[][] V, double[] a)
    {
      for (int i = 0; i < N; ++i) {
        double rowSum = 0.0;
        for (int k = 0; k < K; ++k) {
          double pdf = NaiveProb(x[i], u[k], V[k]);
          w[i][k] = a[k] * pdf;
          rowSum += w[i][k];
        }
        for (int k = 0; k < K; ++k)
          w[i][k] = w[i][k] / rowSum;
      }
    }
    static void UpdateNk(double[] Nk, double[][] w)
    {
      for (int k = 0; k < K; ++k) {
        double sum = 0.0;
        for (int i = 0; i < N; ++i)
          sum += w[i][k];
        Nk[k] = sum;
      }
    }
    static void UpdateMixtureWts(double[] a, double[] Nk)
    {
      for (int k = 0; k < K; ++k)
        a[k] = Nk[k] / N;
    }
    static void UpdateMeans(double[][] u, double[][] w,
      double[][] x, double[] Nk)
    {
      double[][] result = MatrixCreate(K, d);
      for (int k = 0; k < K; ++k) {
        for (int i = 0; i < N; ++i)
          for (int j = 0; j < d; ++j)
            result[k][j] += w[i][k] * x[i][j];
        for (int j = 0; j < d; ++j)
          result[k][j] = result[k][j] / Nk[k];
      }
      for (int k = 0; k < K; ++k)
        for (int j = 0; j < d; ++j)
          u[k][j] = result[k][j];
    }
    static void UpdateVariances(double[][] V, double[][] u,
      double[][] w, double[][] x, double[] Nk)
    {
      double[][] result = MatrixCreate(K, d);
      for (int k = 0; k < K; ++k) {
        for (int i = 0; i < N; ++i)
          for (int j = 0; j < d; ++j)
            result[k][j] += w[i][k] * (x[i][j] - u[k][j]) *
              (x[i][j] - u[k][j]);
        for (int j = 0; j < d; ++j)
          result[k][j] = result[k][j] / Nk[k];
      }
      for (int k = 0; k < K; ++k)
        for (int j = 0; j < d; ++j)
          V[k][j] = result[k][j];
    }
    static double ProbDenFunc(double x, double u, double v)
    {
      // Univariate Gaussian
      if (v == 0.0) throw new Exception("0 in ProbDenFun");
      double left = 1.0 / Math.Sqrt(2.0 * Math.PI * v);
      double pwr = -1 * ((x - u) * (x - u)) / (2 * v);
      return left * Math.Exp(pwr);
    }
    static double NaiveProb(double[] x, double[] u,
      double[] v)
    {
      // Poor man's multivariate Gaussian PDF
      double sum = 0.0;
      for (int j = 0; j < d; ++j)
        sum += ProbDenFunc(x[j], u[j], v[j]);
      return sum / d;
    }
    static double[][] MatrixCreate(int rows, int cols,
      double v = 0.0)
    {
      double[][] result = new double[rows][];
      for (int i = 0; i < rows; ++i)
        result[i] = new double[cols];
      for (int i = 0; i < rows; ++i)
        for (int j = 0; j < cols; ++j)
          result[i][j] = v;
      return result;
    }
    static void MatrixShow(double[][] m, bool nl = false)
    {
      for (int i = 0; i < m.Length; ++i) {
        for (int j = 0; j < m[0].Length; ++j) {
          Console.Write(m[i][j].ToString("F4") + "  ");
        }
        Console.WriteLine("");
      }
      if (nl == true)
        Console.WriteLine("");
    }
    static void VectorShow(double[] v, bool nl = false)
    {
      for (int i = 0; i < v.Length; ++i)
        Console.Write(v[i].ToString("F4") + "  ");
      Console.WriteLine("");
      if (nl == true)
        Console.WriteLine("");
    }
  } // Program class
} // ns

After the template code loaded, at the top of the editor window I removed all unneeded namespace references, leaving just the reference to the top-level System namespace. In the Solution Explorer window, I right-clicked on file Program.cs, renamed it to the more descriptive MixtureModelProgram.cs and allowed Visual Studio to automatically rename class Program.

All normal error checking has been omitted to keep the main ideas as clear as possible. I used static methods and class-scope constants rather than OOP for simplicity.

The Data Structures

The demo code sets constants N = 8 (number of data items), d = 2 (data dimensionality), and K = 3 (number of clusters). There are six data structures. Matrix X is an array-of-arrays with size (8 x 2) and it holds the data. In a non-demo scenario you’d load normalized data into memory from a text file.

Matrix w, which has size (8 x 3), holds the membership weights and the clustering information. Each row of w represents a data item, and each column of w corresponds to a cluster. Mixture model clustering assumes that each cluster follows some probability distribution. The most commonly assumed distribution is the multivariate Gaussian, so the technique is called Gaussian mixture model (GMM). The demo uses a simplified Gaussian, so I call the technique naive Gaussian mixture model, but this isn’t a standard name.

Helper array Nk has 3 cells and holds the sums of the 3 columns of w. Array a has 3 cells and is called the mixture weights. The values in array a sum to 1.0 and are weights that indicate the contribution of each Gaussian distribution to the model. For example, if a = (0.30, 0.60, 0.10), then the first Gaussian distribution contributes 30 percent to the mixture. Each cell of the a array is initialized to 1 / 3 = 0.3333 so that each Gaussian initially contributes equally.

Matrices u and V have size (3 x 2) and hold the univariate means and variances. For example, if u[1][0] = 0.55, the mean for cluster 1, data component 0 (package height), is 0.55. If V [2][1] = 0.33, the variance for cluster 2, data component 1 (package width), is 0.33.

Updating Membership Weights

The membership weights stored in matrix w are updated in each EM iteration according to equation (3) in Figure 2. In words, for each N = 8 rows of w, you compute the K = 3 naive probabilities using the corresponding values of X, u, and V and then multiply each naive probability by the corresponding mixture weight stored in the a vector. After you compute the weighted probabilities for a row, each of the values in the row is normalized by dividing by the row sum.

Updating the membership weights is the expectation step (often shortened to E-step) of the EM optimization algorithm. After membership weights in w are updated, the Nk column sums and mixture weights in vector a are updated using equation (4), the means in matrix u are updated using equation (5), and the variances in matrix V are updated using equation (6) in Figure 2. Updating Nk, a, u and V is the maximization step of EM optimization. The idea is to find the values in a, u and V that best match the data.

Wrapping Up

The code and most of the math notation used in this article are based on a short paper titled “The EM Algorithm for Gaussian Mixtures.” The paper isn’t on a stable Web site and has no author given, but the subtitle and URL indicate the paper is part of lecture notes from a UC Irvine computer science class. You can easily find the paper as a .pdf file by doing an Internet search for the title. I used this somewhat obscure paper because it’s clear and accurate, and almost all of the other Gaussian mixture model resources I found had significant technical errors.

Data clustering is usually an exploratory process. Clustering results are typically examined visually to see if any interesting patterns appear. Most of my colleagues start a clustering investigation by looking at a graph of the source data. If the data appears evenly distributed, then using the k-means algorithm (or one of its many variations) usually works well. But if the data appears skewed, a mixture model clustering approach such as the one presented in this article often gives better results. Most clustering algorithms, including naive Gaussian mixture model, are extremely sensitive to initial values of the means and variances, so these values are often supplied via a preliminary k-means analysis.

Aloha, Servus and Ciao

It’s been a tremendous pleasure writing articles about software development, testing and machine learning for MSDN Magazine over the past 17 years. Rather than look back, I prefer to look forward. The essence of MSDN Magazine has always been filling the gap between low-level software documentation (too much detail) and high-level Hello World-style examples (not enough detail). The need to fill that gap won’t go away so I speculate that the soul of MSDN Magazine will quickly reemerge in an online format of some sort. So goodbye/hello for now and keep your eyes open for MSDN Magazine authors and editors providing useful information in a new format to the software developer community.

– James McCaffrey


Dr. James McCaffrey works for Microsoft Research in Redmond, Wash. He has worked on several key Microsoft products including Azure and Bing. Dr. McCaffrey can be reached at jamccaff@microsoft.com.