Chris Umbel

Nubimus - A Programmatic Dialogue

Nubimus123: Hey, hey!

Pr0gram4tez: Ah, dear Nubimus. What inspires this chat window so close to happy hour on a Friday? Surely not a bug wreaking havoc on your users or offensive performance bottlenecks?

Nubimus123: No, Programates. Quite the opposite, in fact. I was interrupting your work to invite you for a beer to celebrate my team's recent success. Surely you will join me.

Pr0gram4tez: Perhaps, my friend. First tell me about your success. I wish to learn from your work while my head is still clear. My week was full of disappointment and your youthful wisdom and inspiration may be necessary for solutions in the week that follows.

Nubimus123: Gladly, Programetes. The victory was one of cryptography. Our client's data contains highly sensitive personal information and credit card numbers. They sought us out to secure their data that it never be consumed by either hacker or fool.

Pr0gram4tez: By Schneier! The protection of data of that nature is indeed important and critical to the order of the state.

Nubimus123: And that is why they rightly entrusted us with securing it.

Pr0gram4tez: Tell me, fellow developer, how did you achieve the security necessary to meet the requirements of your customer. Did you labor diligently designing an algorithm channelling all of your inventiveness? Did you employ all of your training and education designing a computation that was quick to perform while at the same time married to the highest standard of protection? Did you seek the cutting edge of mathematics and piety? My excitement implores an explanation!

Nubimus123: LOL!!!!!111 I must confess to you, who have always been honest with me. It all boils down to how we got the contract initially. Our bid was low because we allowed the gods to do the work for us. We used the cryto API in the framework crafted by the Olympians themselves.

Pr0gram4tez: How wonderful! It is said that mortals achieve their highest greatness when letting the gods handle the lofty computation so that they may focus their efforts on terrestrial business logic.

Nubimus123: Exactly.

Pr0gram4tez: Tell me, friend Nubimus, what class of divinely-crafted algorithms did you chose? Perhaps a symmetric-key cipher with Hermetic dispatch? Of either the stream or block subtype? Maybe even an asymmetric-key cipher with Alice and Bob unaware of each other's cryptographic secrets?

Nubimus123: SMH... I believe you've failed to learn the very lesson you described mere sentences ago, Programates. Such details are of the divine and, while important, could only be made poorly by simple, mortal software developers. Though I believe the default algorithm for the framework was something called Athena’s Encryption Standard or AES. I'm lead to believe it's the finest cryptographic work produced by god or man. I know not of style or tactics.

Pr0gram4tez: Interesting. The framework is so robust that the implementation required virtually no attention. You've not studied the algorithm itself?

Nubimus123: Like I said. That's a matter for the gods.

Pr0gram4tez: We can agree that the code behind the cryptography is hallowed, but I'm not aware of methods that tailor the implementation to your business case as a matter of course in any framework. There must be much to the implementation, surely. Can you not harm your client if your implementation is incorrect? Could not the gods themselves attempt to injure your business interests?

Nubimus123: Assuredly not, Programetes. The framework's code is not only holy but was developed in pairs and certified by a consortium of deities eliminating the possibility of any one Olympian's mischief or mistake.

Pr0gram4tez: The first question remains unanswered, methinks. It is not possible for you to hinder the service with even the most well-intentioned practical application?

Nubimus123: Perhaps a layman. We are a professional team. We may not be gods, but I'm confident we consumed the framework as intended. Besides, the gods will assist us. We’ve prayed to them in social media. We’ve sacrificed to them with licensing fees.

Pr0gram4tez: The admission of the potential fallibility of an implementation using the framework troubles me, even if by a neophyte. I can accept the quality of the framework as it stands but you've only offered the credentials of your team as proof the implementation is correct. You say you're professionals, but admit that you accepted the algorithm as a default without significant technical consideration. Did you investigate other algorithms offered by the framework? They’re equally divine and perhaps better suited to your toil.

Nubimus123: No, old friend. We have to deliver! Besides, the customer will be satisfied with reasonable security. What they're really paying for is our work-flow and interface. There's no way parameters to a cryptographic problem will significantly impact the effective security of our application in a negative way within the realm of practicality.

Pr0gram4tez: Whether implemented by either god or man are the cryptographic parameters not identically effective or ineffective? Speed and stability may be the domain of the gods, but would a small key size make you equally vulnerable regardless of the algorithm's origin? Can you not impart your human fallibility on these holy tools?

Nubimus123: I must admit, Programetes, I'm unaware of the practical impact of key size in our implementation, but it's certainly adjustable. It's a problem easily solved with configuration. It's still overwhelmingly in the hands of the gods who sold us the framework.

Pr0gram4tez: Very well, Nubimus. But keep in mind even with adequately-sized keys there are more cryptographic parameters such as the mode of operation. Even if adjustable at deploy-time are you aware of them now? Can you guarantee the data provided by your client will be ciphered optimally upon delivery so you don't have to trouble them for migrations later?

Nubimus123: Another admission, oh thorough Promgrametes, follows. I have no answer as I've not studied modes of operation, but can note it for consideration with the team next week. Again, likely a matter of configration. You've proven the need for an audit so our configuration is correct upon delivery, but nothing has been stated yet indicating code needs changed or that our choice was flawed.

Pr0gram4tez: Please understand my goal is not to convince you to change your code, dear Nubimus! I'm simply trying to understand how productive the framework your team uses is. Perhaps I've turned up some points that require attention as is common for a grey-beard like myself. I do have some questions that are somewhat higher-level if you have time before we leave.

Nubimus123: My thirst grows, and my brain becomes weary on this Friday afternoon, but in exchange for the service you've provided I will certainly entertain them.

Pr0gram4tez: My gratitude is immense, Nubimus. Well then, the keys... Does the framework you employ provide a secure key storage facility?

Nubimus123: It does indeed, but we don't use it. In effort to centralize our data we store the keys directly in the application's relational store.

Pr0gram4tez: I'm puzzled, Nubimus. You store the ciphertext in the relational store, correct?

Nubimus123: That is true, Progametes.

Pr0gram4tez: And, as you've stated, the keys are in the relational store. All of them?

Nubimus123: Quite true.

Pr0gram4tez: So in the event of a compromise, beg the gods not, an adversary, perhaps an agent of Syracuse, would have all the tools necessary to recover the plaintext and use it against you or your clients or Athens itself?

Nubimus123: Well, I suppose so, Programetes. That seems unlikely, though. The attacker would have to compromise multiple, higher-level layers to achieve access.

Pr0gram4tez: What evidence do you have that any other layer of your application isn't equally vulnerable? Have you not used the same strategy of trusting the framework above the judgement of mortal programmers? Is not the desired result of cryptography to protect your clients data in the event the higher levels of the application are compromised?

Nubimus123: Well, I fear that may be true, rare friend.

Pr0gram4tez: Tell me, then. Is the cryptography not potentially rendered irrelevant by your key storage practices. Is the net result that there is little security benefit but there is complexity incurred in the application?

Nubimus123: I admit that my understanding, and perhaps that of the whole team, of how to implement a cryptographic system was inadequate. We became emboldened by the code crafted by the gods and it made us feel invincible. I now know that a tool of the gods in the hands of man doesn't make that same man godlike.

Pr0gram4tez: I regret that our conversation may leave you with your mood diminished, Nubimus. I am sorry for that. It was not my intention to play your adversary, but I am thankful that you and your clients may benefit.

Nubimus123: And I thank you for it, teacher Programetes. Regardless, my eyes can focus on pixels no longer. It's time to close this chat window.

Pr0gram4tez: Beer, then?

Nubimus123: I've changed my mind, Programetes. I feel I must stay sharp for a busy weekend of study.

Sat Mar 10 2012 11:50:32 GMT+0000 (UTC)

Comments

The Sylvester Matrix Library Comes to Node.js

Before some recent machine learning implementations in node I set out to find a reasonable matrix math/linear algebra library for node.js. The pickings were slim but I managed to dig up a general JavaScript matrix math library written by James Coglan called sylvester. Clearly, sylvester had to be node-ified.

With the help of Rob Ellis (a collaborator on natural) it's been wrapped up into a node project titled node-sylvester & NPM and has had some features added such as element-wise multiplication, QR, LU, SVD decompositions and basic solving of systems of linear equations.

In this post I'll cover some of the basic structures and operations supported by sylvester, but it will by no means be complete. I'll focus solely on the Matrix and Vector prototypes, but sylvester also supports Line, Plane, and Polygon. Also within the covered prototypes I'll only demonstrate a small, but useful subset of their functionality.

Currently, the only reasonable sources of documentation for functionality existing only in the node port are in the README while general sylvester functionality is covered in its API docs. In time I will (hopefully with the help of the community) provide some more complete documentation.

Installation

Getting ready to use the node port of sylvester is what you'd expect, a standard NPM install.

npm install sylvester

You can then require-up sylvester in your node code and use the prototypes within.

var sylvester = require('sylvester'),
  Matrix = sylvester.Matrix,
  Vector = sylvester.Vector;

Vector and Matrix Prototypes

Matrices and vectors are abstracted by the Matrix and Vector prototypes.

Instances can be created using their create functions and passing in an array of values. Vector.create accepts a one dimensional array of numbers and Matrix.create accepts multiple dimensions.

var x = Vector.create([1, 2, 3]);
var a = Matrix.create([[1, 2], [3, 4]]);

representing:

Global shortcuts exist to clone Vector and Matrix prototypes with $V and $M respectively. The following is the semantic equivalent of the previous example.

var x = $V([1, 2, 3]);
var a = $M([[1, 2], [3, 4]]);

Matrix's Ones function will create a matrix of specified dimensions with all elements set to 1.

var ones = Matrix.Ones(3, 2);

Matrix's Zeros function does the same except with all elements set to 0.

var zeros = Matrix.Zeros(3, 2);

An identity matrix of a given size can be created with Matrix's I function.

var eye = Matrix.I(4);

Data Access

Values can be retrieved from within a Matrix or Vector using the e method.

a.e(2, 1); // returns 3
x.e(3); // returns 3

The entire set of values can be retrieved/manipulated with the elements member of Matrix and Vector. elements is an array-of-arrays-of-numbers in the case of matrices and it is a simple array-of-numbers for vectors.

var a_elements = a.elements; // [[1, 2], [3, 4]]
var x_elements = x.elements; // [1, 2, 3]

Basic Math

Many standard mathematic operations are supported in Vector and Matrix clones. In general the arguments can either be scalar values or properly-dimensioned Matrix/Vector clones. While not covered here element-wise versions of many multiplicative/specialized operations are also available.

Matrices and vectors can be added-to and subtracted-from by scalar values using the add and subtract functions.

var b = a.add(1);
var d = a.subtract(1);

symbolizing:

Naturally, like-dimensioned matrices and vectors can be added to and subtracted from each other.

var e = a.add($M([[2, 3], [4, 5]]));
var f = a.subtract($M([[0, 2], [3, 3]]));

meaning:

Matrices/vectors can be multiplied with matrices/vectors of appropriate dimensions.

var g = a.x($V([3, 4]));

The dot-product of two vectors can be computed with Vector's dot method.

var y = x.dot($V([4, 5, 6]));

depicting:

Transposing

A Matrix can be transposed with the transpose method.

var at = a.transpose();

where at represents the matrix:

Segmentation/Augmentation

The first n elements can be removed from a Vector with the chomp method.

var n = 2;
var xa = x.chomp(n);

In contrast the first n elements can be retrieved with the top method.

var xb = x.top(n);

Vectors can have a list of values appended to the end with the augment method.

var xc = x.augment([4, 5]);

Matrices can have a column appended to the right with Matrix's augment method.

var m = a.augment($V([3, 5]));

A sub-block of a Matrix clone can be retrieved with the slice method. slice accepts a starting row, ending row, starting column and ending column as parameters.

var aa = $M([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
var ab = aa.slice(1, 2, 1, 2);

The code above produces a matrix ab shaped like:

Decompositions/Advanced functions

A small number of decompositions are currently supported. These have all been recently added and at the time of writing are, while functional, not necessarily as computationally efficient as they could be. Also their stability cannot be guaranteed at this time.

Note that these were all implemented in pure JavaScript. My personal hope is to keep node-sylvester 100% functional in pure JavaScript to maintain the best compatibility across all platforms. In the long run, however, I hope to employ time-tested, computationally-efficient native libraries to perform these operations if the host machine allows.

A QR decomposition is made possible via Matrix's qr method. An object is returned containing both the Q and the R matrix.

var qr = a.qr();

resulting in the object:

{ Q: 
   [-0.316227766016838, -0.9486832980505138]
   [-0.9486832980505138, 0.3162277660168381],
  R: 
   [-3.162277660168379, -4.427188724235731]
   [5.551115123125783e-16, -0.6324555320336751] }

Singular Value Decompositions (SVD) can be produced via Matrix's aptly named svd method. An object is returned containing the U (left singular), S (singular diagonal), and V (right singular) matrices.

var svd = a.svd();

which returns:

{ U: 
   [0.40455358483359943, 0.9145142956773742]
   [0.9145142956773744, -0.40455358483359943],
  S: 
   [5.4649857042190435, 0]
   [0, 0.36596619062625785],
  V: 
   [0.5760484367663301, -0.8174155604703566]
   [0.8174155604703568, 0.5760484367663302] }

Principal Component Analysis can be performed (dimensionality reduced) and reversed (dimensionality restored with approximated values) using Matrix's pcaProject and pcaRecover methods respectively.

pcaProject accepts the number of target dimensions as a parameter and returns an object with the reduced data Z and the covariant eigenvectors U.

var pca = a.pcaProject(1);
{ Z: 
   [2.2108795577070475]
   [4.997807552180416],
  U: 
   [0.5760484367663208, -0.8174155604703633]
   [0.8174155604703633, 0.5760484367663208] }

and pcaRecover approximately recovers the data to the original dimensionality.

pca.Z.pcaRecover(pca.U);   

which produces:

Solving Linear Equations

Simple linear equations in the form of

can be solved using the suitability-named solve method.

var A = $M([
    [2, 4],
    [2, 3],
]);

var b = $V([2, 2]);
var x= M.solve(b);

which is representative of

Conclusion

Thanks to James Coglan's hard work on sylvester plenty of matrix math functionality has been exposed to JavaScript. With a little extra work we've been able to expose quite a bit more to node. There's still more work to do not only in optimization but also in capabilities. In time hopefully node-sylvester will become fast and complete.

As a reminder, the functionality outlined above is not representative of the full list of current capabilities. Between the README in the source and the sylvester API documentation you should be able to get a reasonably clear picture. Still, if you're looking for ways to help with the project a concerted documentation effort would be wonderful and appreciated.

Sat Dec 17 2011 05:00:00 GMT+0000 (UTC)

Comments

Io, A Beautiful, Prototype-Based Language

I recently started reading Seven Languages in Seven Weeks by Bruce A. Tate and intended to ultimately post a review. Something went wrong along the way, however. On the second language covered in the book I became so intrigued that I had to dedicate a single post to it immediately. That language is Io, a prototype-based language created by Steve Dekorte.

Now, you have to keep in mind that I've only been hacking Io for about three days. I'm not an expert. I can't promise any of the examples I'm going to provide are idiomatic, robust or performant. I can't promise any of my advice will be prudent or that this article can even properly tutor you on Io.

I can, however, make the strong recommendation that you give Io a shot especially if you're a novice JavaScript programmer. Being prototype-based and syntactically simple Io can help an aspiring JavaScripter truly understand the patterns without the baggage that comes along with JavaScript. I can only promise an honest attempt at whetting your appetite.

Here I'll just outline some examples rather than deliver thorough instruction. For that Seven Languages in Seven Weeks does a fine job (most of these examples are adapted from its exercises) and the Io Guide can be a wonderful help as well.

Prototype-based

As I mentioned several times already Io is prototype-based. There are no classes yet there are objects. Objects can clone other objects providing an inheritance mechanism. Consider the following example.

Thing := Object clone

That code created a prototype named "Thing" from the super-prototype "Object". Note the ":=" assignment operator. That essentially creates the destination if it does not exist yet. Had "Thing" already been assigned to "=" would have sufficed as it can only assign an existing slot (slots are the named locations in an object where data members and methods can be stored).

Now lets add a method to the "Thing" prototype. The following code will add a method into the "printMessage" slot of the "Thing" prototype.

Thing printMessage := method(
    writeln("Hello, thing!")
)

We can clone the "Thing" prototype into an instance and call the "printMessage" as such.

thing := Thing clone
thing printMessage

which would output

Hello, thing!

Io thinks of things in terms of message-passing. Rather than saying, "we just called thing's printMessage method" we should say, "we just sent a printMessage message to thing."

Note that we cloned into a lower-case "thing". When you're defining a pure prototype you start the identifier with a capital and instances with lower case.

Now lets add a data member to the instance and method to the prototype to demonstrate parameters and encapsulation.

Thing calculatePrice := method(markup,
    self price + markup
)

thing price := 10
thing calculatePrice(2) println

which outputs

12

That example also demonstrates chaining messages together. calculatePrice returned a Number and we in turn pass a println message to the number (as an alternative to passing the number itself to writeln).

Metaprogramming

One of the exercises covered in Seven Languages in Seven Weeks that I found interesting was changing the behavior of Io itself so that division by zero would result in a 0 not infinity. Of course, you'd probably never want to do that, but it's a wonderful example of how much control you have over the runtime itself.

For instance if you execute the following in Io without any additional intervention

(6 / 0) println

you get

inf

Now let's reach into Io and change how division works.

Number oldDiv := Number getSlot("/")

Number / := method(d,  
  if(d == 0, 0, self oldDiv(d))
)

(6 / 2) println
(6 / 0) println

outputting

3
0

The first output was done just to illustrate that we didn't break division entirely. The second illustrates that we sure did change how division by 0 works, rather than "inf" we got "0".

Let's break it down line by line.

Number oldDiv := Number getSlot("/")

That may look reasonable to a Rubyist who would "alias" in a situation like this. We're basically copying the division operator's (the method in slot "/") logic into another slot named "oldDiv". Since we're going to rewrite "/" we'll want to keep the functionality around for later use and "oldDiv" is a fine place.

Number / := method(d,  
  if(d == 0, 0, self oldDiv(d))
)

Now we've changed the "/" method of Number. If the denominator (the lone parameter) is zero we will return zero. Otherwise we rely on the "oldDiv" to perform normal division.

DSLs/Structured Data Parsing

As a Rubyist by trade I always chuckle when I hear .Net or Java programmers claim that Domain Specific Languages are a waste of time. From their perspective the time investment is far greater than any value that might be yielded. Nine times out of ten they're probably right. They work with tools with a fixed idea of what instructions should look like.

Io gives you a great deal of control over the language's parser itself. Rather than writing a parser or implementing a DSL within the confines of the host language you can teach Io to parse and evaluate your DSL within its own interpreter! In Io the line between internal and external DSLs is somewhat blurred and I think that's just fantastically nifty.

We're going to write a little JSON parser here. Sure, there are probably better ways of parsing JSON with Io but it provides an effective example that's easy to relate to.

Take a file named "test.json" with the following content as given:

{
  "name": "Chris Umbel",
  "lucky_numbers": [6, 13],
  "job"	: {
    "title": "Software Developer"
  }
}

The following code will parse the JSON data, albeit liberally. This is meant to be demonstrative, not robust.

OperatorTable addAssignOperator(":", "atPutNumber")

curlyBrackets := method(
  data := Map clone
  call message arguments foreach(arg,
    data doMessage(arg))
  data
)

squareBrackets := method(
  arr := list()

  call message arguments foreach(arg,
    arr push(call sender doMessage(arg)))

  arr
)

Map atPutNumber := method(
  self atPut(
    # strip off leading and trailing quotes
    call evalArgAt(0) asMutable removePrefix("\"") removeSuffix("\""),
    call evalArgAt(1)
  )
)

s := File with("test.json") openForReading contents
json := doString(s)

json at("name") println
json at("lucky_numbers") println
json at("job") at("title") println

which will output

Chris Umbel
list(6, 13)
Software Developer

Now to break it down.

OperatorTable addAssignOperator(":", "atPutNumber")

Here we told Io to accept a brand spanking new assignment operator with the text ":". It will then pass the argument along to the target object via the "atPutNumber" message.

curlyBrackets := method(
  data := Map clone
  call message arguments foreach(arg,
    data doMessage(arg))
  data
)

That instructs Io what to do when it comes across a curly bracket when parsing code. In our case it it creates a Map and begins to fill it. "call" performs reflection on the argument data passed to the method. "call message arguments" accesses the list of all arguments recieved.

squareBrackets := method(
  arr := list()
  call message arguments foreach(arg,
    arr push(call sender doMessage(arg)))
  arr
)

Here we instructed Io how to deal with JSON arrays. Per the slot name it builds a list of all elements enclosed in square brackets.

Map atPutNumber := method(
  self atPut(
    # strip off leading and trailing quotes
    call evalArgAt(0) asMutable removePrefix("\"") removeSuffix("\""),
    call evalArgAt(1)
  )
)

The Map prototype has been given a atPutNumber method that will strip quote off of the element names and slap the value specified by the JSON into the corresponding value in the Map. "evalArgAt" grabs the argument data at a specified index.

s := File with("test.json") openForReading contents
json := doString(s)

Now there's the money. We loaded up the JSON document and slapped its contents in a string. We then essentially eval it with "doString" letting the Io interpreter do the dirty work.

Message Forwarding

In Ruby something called "method_missing" is relied upon to handle methods of arbitrary names at runtime. The Io equivalent is "forward". The following example supplies a simple mechanism to build XML like:


  
    
      The Thing
    
    
      Horror
    
  

with code like:

builder := Builder clone
builder movies(
  movie(
    title("The Thing"),
    genre("Horror")
  )
)

Here's the code to make it happen.

Builder := Object clone do(
  depth := 0
)

Builder indent := method(
  depth repeat(
    write("  ")
  )
)

Builder emit := method(
  indent

  call message arguments foreach(arg,
    write(call sender doMessage(arg))
  )

  writeln
)

Builder emitStart := method(
  emit("<", call evalArgs join, ">")
)

Builder emitEnd := method(text,
  emitStart("/", text)
)

# handles messages for non-existant methods. 
Builder forward := method(
  emitStart(call message name)
  depth = depth + 1

  call message arguments foreach(arg,
      content := self doMessage(arg)
      if(content type == "Sequence",
        emit(content)
      )
  )

  depth = depth - 1
  emitEnd(call message name)
)

builder := Builder clone
builder movies(
  movie(
    title("The Thing"),
    genre("Horror")
  )
)

Now that's a big example, but let me extract the section of most relevance.

# handles messages for non-existant methods. 
Builder forward := method(
  emitStart(call message name)
  depth = depth + 1

  call message arguments foreach(arg,
      content := self doMessage(arg)
      if(content type == "Sequence",
        emit(content)
      )
  )

  depth = depth - 1
  emitEnd(call message name)
)

By implementing a method in the "forward" slot we allow the Builder prototype to accept messages of any name. Our "forward" method then obtains the name of the message sent with "call message name" and handles it accordingly (making a node of that name).

Next Steps

One thing not covered here is the concurrency story, and Io really shines there. Easy coroutines, actors and futures are available to provide refreshing simplicity to what's typically a tricky problem.

There are also reasonable libraries available for common tasks like networking and XML parsing.

Check out the Io Guide for information on these and other topics. Of course Seven Languages in Seven Weeks is wonderful as well.

Tue Sep 06 2011 03:00:00 GMT+0000 (UTC)

Comments

The node.js Natural Language Story

In early May of 2011 I started work on natural, a general Natural Language Processing module for node.js. I was loosely basing the idea off of the ever-popular Natural Language ToolKit (NLTK) for python. I wanted to create a one-stop shop for NLP but for the node.js platform.

I'm excited to see that I'm not the only one with an interest in NLP under noedejs. Considering there's no way I can be totally comprehensive with natural it's imperative that the community is hacking away, building a great NLP story for node.

Here I'm going to outline the interesting node NLP projects that I've found so far.

Projects

natural - In some shameless self-promotion I'll list myself first:) Like I mentioned above, natural is a general natural language facility for node.js written by yours truly. Stemming, classification, phonetics, n-grams, tf-idf, WordNet, and some inflection are currently supported.

pos-js - Here's an excellent part of speech tagger by Percy Wegmann and Gerad Suyderhoud. It's a port of Mark Watson's FastTag Part of Speech Tagger for Java which in turn uses Eric Brill's POS ruleset.

glossary - Here's an auto tagger written by Heather Arthur which can extract keywords from text.

reds - a Redis Full-text search implementation by the prolific TJ Holowaychuk.

tfidf - an easy to use text frequency-inverse document frequency library for Node.js by Linus G Thiel of Hansson & Larsson.

Lingo - a general linguistics module by TJ Holowaychuk which does inflection, translation, and some casing.

nlp-node - rule-based NLP tools for node including date extraction and inflection by Spencer (not sure he wants his last name given).

Know of any others? Contact Me!

Help Me!

And finally I'd like to ask for help with natural. I'd love to make it as comprehensive as possible and there are a mountain of algorithms to implement for English alone. Also, I'm interested in supporting algorithms for other languages as well. If you have the capacity and interest let me know.

Sat Aug 20 2011 04:00:00 GMT+0000 (UTC)

Comments
< 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 >
Follow Chris
RSS Feed
Twitter
Facebook
CodePlex
github
LinkedIn
Google