Showing posts with label corpora. Show all posts
Showing posts with label corpora. Show all posts

Tuesday, 1 February 2011

summing logs

Got a bit of hacking to do today for the corpus acquisition stuff.  Trying to work out why our sentence extraction (or deletion) depending on your POV works so badly.

Trying to evaluate results (informally) is a bit of a tricky enterprise for me.  I tend to just rely on your basic Unix text processing tools and graphical diff.  So if example, I want to see what our tool, which is supposed to delete boring sentences from a text actually does, what I'll do is to run cd before; wc -l -w *.txt and the same with cd after.  This gives me the number of lines and tokens in each set of texts.  Assuming we're working on the same files, I can then view the two word count files, finding out for example that there are no differences (which means we haven't been deleting anything), or that there are systematically zero tokens/lines in the after end... which means something has gone wrong.  In my case, both are true...

I think I've worked out what it is.  Either I was using some kind of crude method before (deliberately), or I just misunderstood some idea somewhere (likely), but in any case I've got some logarithms to sum.  Sure wish I had maths more firmly anchored in my brain.  I can think thoughts like "oh, summing logs of probabilities is like multiplying them, which makes sense if you've got a reasonable independence assumption and you want to see the joint probabilites"... but then if you push my brain just a little further "so what does it mean when take the mean of these sums" it explodes.  Puny Erician brain...

Monday, 24 January 2011

alignment what?

The difficulty in working on multiple projects is when you have to switch gears and pick up a dormant project after a few months.  That's where I am with this corpus acquisition stuff.  My mission this week is to get to grips with what I was doing before I put this stuff aside.  Hopefully as I get better at using tools like Inkscape and at figuring out what exactly it is that causes each future Eric to go "huh?", this will be smoother.  But of course when you are in the heat of things, you never really have time to do things right...

Bogus thinking, of course.

Doing things wrong is expensive.

Monday, 3 January 2011

joshua

Happy New Year!  I'm sort of at an in-between-tasks phase.  Shifting gears is always annoying.  Still at the tail end of the evaluation stuff, and there's little bits and pieces of boring clean up I could do, but eh.  Need to be moving on to the mountains stuff.  As you may have seen in my last talk, we want to figure out how to do a better job building corpora for NLG.  But to figure out how good a job we're doing, we also need to put said corpora to use for training stuff.  So I figured one of things I'd better be trying to train is Joshua, fully automated SCFG based machine translation...

Always find this sort of thing to be a pain, but somebody's gotta do it.

Thursday, 30 September 2010

louvre stuff sucked down

OK, so I managed to finish yesterday tagsoup adventure. Ran into some bits where in retrieving pages from the louvre site, I was getting funny characters. This was due to using String as the internal representation of the data but without actual proper Unicode code points in the Chars (I think). I struggled for a bit trying to get the HTTP library to use ByteString instead (it accepts both), but gave up on some type inference problem [:-( !]. Instead I just told the IO stuff to save the files as binary, ie. just treat the Chars as padded bytes rather than trying to encode them. Anyway, I now have a sort of parallel corpus of 275 texts in English, 303 in French (so some missing English translations). English texts have been processed with the automatic extractor.

Storing this thing is a bit awkward since I don't know the best practices. You have lots of tiny decisions to make like:
  • how to store things? just files on the filesystem or through some mechanism like a database engine
  • if the former (pro: Eric knows what he's doing), then how do you structure the directories/files? I have {fr,en}{,-orig,-processed} So note the choices there: relatively flat structure (no directory hierarchy), and files "repeated" across the directories. The same file at various stages of processing might be fr-orig/foo.html, fr/foo.txt, fr-processed/foo.txt.debug. Maybe this could have all gone into the same directory fr/.
  • how do you name the files? I have my files named with a unique identifier from the Louvre, so I get things like fr/work-10134198673225779 and en/work-10134198673225779. The unique identifier is easy to derive (grab it from the URL parameters) and it allows you represent that a text has different translations (ie. it's the same name in fr/en). But it's also totally opaque to me as a human. Not good
So I guess there's three basic tracks I want to cover next:
  1. Packaging everything up: updating the README and virtual machine to cover the latest infrastructure (I may be going away at the end of October, so I want this done early)
  2. Analysing the output (this time focusing on the Louvre stuff and probably forgetting about Sussex Sculptures)... which I keep putting off because it's a Thinky task rather than a Do-ey one.
  3. Improve the infrastructure (this sort of depends on #2). For example, the current RMRS based extractor is really dumb: it just assumes that any two grandchildren of a verb node are its arguments in a relation, without actually paying attention to which one should be the subject and which one should be the object. RMRS actually tells us enough to distinguish between the two, but I just wanted something quick and dirty which works. I could also (somewhat reluctantly) think about rewriting the extractor in Scala. This would minimise the number of languages being used in the infrastructure, allow for easier plugging into GATE (no spawning processes, just invoke a method), and also give me a chance to learn more Scala.

Friday, 19 February 2010

the "one-off" directory

Another useful convention when building pipelines or just doing any sort of computer-assisted (corpus driven?) researchy/academic task.

Problem

Sometimes you just need to do one-time transformations on your data. Just this once, you need to grep your data for all the pictures of people with big noses in them. Or just this once, you need to find the ones with funny hats.

Integrating it into your pipeline is kinda silly because you really only just need this thing fast, and like, just this once. It imposes a kind of cognitive overhead that leads to paralysis. Really, this is the sort of thing you just bang out on the command line and be done with it...

Except of course...

It's never really just the once is it?

Proposed solution

Within the project directory (eg. hills-corpus), create a directory called "one-off". The idea is to have a place where you can sweep up all your disposable code. Things you type out on the command line, you should really copy and paste and turn into "one-off/some-foo.sh".

Why I think this works

First: this means you preserve your operation for posterity. I've used some of my supposedly "one-off" scripts (be they in Bash or Haskell) dozens of times. Not so one-off after all.

Second: they reduce the cognitive overhead of trying to figure out what the "right" place to put them into your pipeline (eg. Makefile of Doom) is. You don't even really have to give them sensible names either, because they're really just swept up under the one-off rug, and you don't really expect to see them again anyway (oh but you will, my friend, you will...)

Third: it eliminates clutter. Previously, I was dumping these one-off scripts in my project root (for fear of losing them). The problem with clutter is that you come back to it 3 months later, and you have no idea what you can safely delete, on what you need to hang on to. Now they have some place to go. In the worst case, they go, but that's not so bad.

Thursday, 17 December 2009

corpus sifter - tool for building paired data/text corpora

Here's a small webapp I wrote yesterday. Since the app (as I envisoned it) was fairly simple, I figured I would invest some time to learn how to write web stuff in Haskell. I went with the Happstack framework just because it's the most well known.



Sales pitch

I call the app "corpus sifter". It could use a nicer name. The code is BSD licensed and I'm willing to stick the Darcs repository up somewhere, maybe even upload it to hackage if anybody is interested, but I doubt it's useful enough to justify such actions.

The scenario behind this application is as follows:
  • You have some pieces of structured data
  • Using a search engine, you have retrieved a text file for each piece of the data
  • You don't know if the data you retrieved really corresponds to the data; there may be some automatic ways of finding out, but you need to spend a little time scrutinising the data to find out
Solution? Well, you're stuck with manually sifting through the data, so you might as well make this manual process as quick and painless as possible. To this end, we present the data and text file side-by-side, and three buttons "good", "bad" and "unsure". Clicking on one of these buttons moves the pair to the corresponding "good/bad/unsure" bin from the new bin; it also presents you with another item to classify.

Two features of this approach is that is (a) fairly general and (b) simple/robust (hopefully).

generality

There is nothing in corpus sifter that is specific to the corpus in question. The data must be represented as a JSON object (which corpus sifter will render as an HTML table), and the text will just be systematically dumped out as raw text (these are wikipedia entries that I'm using, but you may have other things in mind).

simplicity/robustness?

Part of corpus sifter's design is to account for incremental corpus construction. When you begin building the corpus, you may not exactly know all the best ways of (i) sifting through it or (ii) searching for texts. So over time, you may want to revise your data. Corpus sifter supports this by adopting a very simple and stupid model of processing: the bins are represented as directories, and moving items from one bin to another consists of literally moving them on the file system.

If you change your mind about a bin (say, you hit a back button), corpus sifter will notice and move the data pair from the corresponding bin.

If over time you add more data to the corpus that you want to sift through, just dump it in the "new" directory.

In a future version of corpus sifter (maybe tomorrow?), I plan to add a feature that lets it notice that you have put something in the "new" bin which already has been classified. This would account for cases where you have new, hopefully better search results. What the tool would hypothetically do is to create some sort of backup directory and move the old data from its bin to this directory. This will be handy for cases where you incrementally refine your search methodology, leading to different results for files that you have already seen. For example, working with Wikipedia entries, I've discovered better and better ways of coping with disambiguation pages, but I need to find some graceful solution for merging these new results in with my old sifted corpus. I certainly wouldn't want to have two copies of my data lying around!

Implementation notes

You can count on me to extol the virtues of Haskell programming at any slightest opportunity. Today, the features I'm going to gush about are common among many functional programming languages (Haskell, ML-ish languages and Scala). These features are algebraic data types and pattern matching.

How Haskell helped me here is to develop a simple and generic solution for rendering any JSON object as HTML. The code is as follows (renderer in 25 lines of Haskell, entire app in about 200 lines):
jsonToHtml JSNull = noHtml
jsonToHtml (JSBool b) = stringToHtml (show b)
jsonToHtml (JSRational float r) =
stringToHtml $ if denominator r == 1 then show (numerator r)
else show (fromRational r :: Float)
jsonToHtml (JSString js) =
case fromJSString js of
u@('h':'t':'t':'p':':':'/':'/':u') -> anchor (abbreviate u') ! [ href u ]
s -> stringToHtml s
where
abbreviate = stringToHtml . (++ "...") . take 13
jsonToHtml (JSArray xs) = unordList (map jsonToHtml xs)
jsonToHtml (JSObject o) = jTable . concatHtml . altMap dark light . map mkRow $ fromJSObject o
where
mkRow (k,v) = tr $ concatHtml [ td (stringToHtml k), td (jsonToHtml v) ]
dark = (! [ theclass "dark" ])
light = (! [ theclass "light" ])
jTable x = table x ! [ theclass "json" ]

-- | map on every other element
altMap _ _ [] = []
altMap f1 f2 [x] = [ f1 x ]
altMap f1 f2 (x1:x2:xs) = f1 x1 : f2 x2 : altMap f1 f2 xs


The basic idea is that JSON strings are escaped and turned into straight HTML, numbers are represented in decimal notation (where appropriate), lists are rendered as bullets (ul tag) and here's where it gets fun, objects (key-value pairs) as tables. The nice thing here for me is that I didn't have to go read a bunch of specs to know what I had to do. Since the author of the json library chose a nice simple sum type representation for JSON objects, I could simply write a function to pattern-match on each one of the cases individually...

These key is that algebraic types helped me to think about the problem because they (i) divided the problem into cases which (ii) they made very readily apparent and (iii) which the compiler could then check that I have exhaustively explored all the cases [eg, did you consider boolean values in JSON? what about the null case?]. Furthermore, the recursion on the the types (JSON objects contain JSON objects) was very simple to deal with as just a straightforward recursive function.

Pattern matching and algebraic data types... this kind of stuff makes me really reluctant to give up statically-typed functional programming languages. Of course, there must be other ways to solve this with other languages' tools; if this would have been easier in some other language, give me a shout in the comments.