Rubyでword2vecを呼び出す(C拡張)

# Rubyでword2vec使えたら良いのにということで
* 参考に書いてあるC拡張のチュートリアルを参考にさせて頂きながらdistance.cを書き換えコンパイル
* irbで呼び出してみます

***

まずは, 準備です.

- distance.c

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <malloc/malloc.h>
#include <ruby.h>

const long long max_size = 2000;
const long long N = 50;
const long long max_w = 50;

FILE *f;
char st1[max_size];
char *bestw[N];
char file_name[max_size], st[100][max_size];
float dist, len, bestd[N], vec[max_size];
long long words, size, a, b, c, d, cn, bi[100];
char ch;
float *M;
char *vocab;

VALUE cDistance;

VALUE open_func(VALUE self)
{
  f = fopen("vectors.bin", "rb");// ファイル読み込み
  fscanf(f, "%lld", &words);
  fscanf(f, "%lld", &size);

  vocab = (char *)malloc((long long)words * max_w * sizeof(char));

  for (a = 0; a < N; a++) bestw[a] = (char *)malloc(max_size * sizeof(char));
  M = (float *)malloc((long long)words * (long long)size * sizeof(float));
  if (M == NULL) {
    return -1;
  }
  for (b = 0; b < words; b++) {
    a = 0;
    while (1) {
      vocab[b * max_w + a] = fgetc(f);
      if (feof(f) || (vocab[b * max_w + a] == ' ')) break;
      if ((a < max_w) && (vocab[b * max_w + a] != '\n')) a++;
    }
    vocab[b * max_w + a] = 0;
    for (a = 0; a < size; a++) fread(&M[a + b * size], sizeof(float), 1, f);
    len = 0;
    for (a = 0; a < size; a++) len += M[a + b * size] * M[a + b * size];
    len = sqrt(len);
    for (a = 0; a < size; a++) M[a + b * size] /= len;
  }
  fclose(f);
  return self;
}

VALUE caliculate_func(VALUE self, VALUE s_val)
{
  char* s = StringValuePtr(s_val);

  for (a = 0; a < N; a++) bestd[a] = 0;
  for (a = 0; a < N; a++) bestw[a][0] = 0;

  strcpy(st1, s);

  cn = 0;
  b = 0;
  c = 0;
  while (1) {
    st[cn][b] = st1[c];
    b++;
    c++;
    st[cn][b] = 0;
    if (st1[c] == 0) break;
    if (st1[c] == ' ') {
      cn++;
      b = 0;
      c++;
    }
  }
  cn++;
  for (a = 0; a < cn; a++) {
    for (b = 0; b < words; b++) if (!strcmp(&vocab[b * max_w], st[a])) break;
    if (b == words) b = -1;
    bi[a] = b;
    if (b == -1) {
      break;
    }
  }

  for (a = 0; a < size; a++) vec[a] = 0;
  for (b = 0; b < cn; b++) {
    if (bi[b] == -1) continue;
    for (a = 0; a < size; a++) vec[a] += M[a + bi[b] * size];
  }
  len = 0;
  for (a = 0; a < size; a++) len += vec[a] * vec[a];
  len = sqrt(len);
  for (a = 0; a < size; a++) vec[a] /= len;
  for (a = 0; a < N; a++) bestd[a] = -1;
  for (a = 0; a < N; a++) bestw[a][0] = 0;
  for (c = 0; c < words; c++) {
    a = 0;
    for (b = 0; b < cn; b++) if (bi[b] == c) a = 1;
    if (a == 1) continue;
    dist = 0;
    for (a = 0; a < size; a++) dist += vec[a] * M[a + c * size];
    for (a = 0; a < N; a++) {
      if (dist > bestd[a]) {
        for (d = N - 1; d > a; d--) {
          bestd[d] = bestd[d - 1];
          strcpy(bestw[d], bestw[d - 1]);
        }
        bestd[a] = dist;
        strcpy(bestw[a], &vocab[c * max_w]);
        break;
      }
    }
  }

  for (a = 1; a < N; a++) {
    strcat(bestw[0], " ");
    strcat(bestw[0], bestw[a]);
  }

  return rb_str_new2(bestw[0]); //bestwをスペース区切りにしてStringに型変換
}

void Init_distance()
{
  cDistance = rb_define_class("Distance", rb_cObject);
  rb_define_method(cDistance, "open", RUBY_METHOD_FUNC(open_func), 0);
  rb_define_method(cDistance, "caliculate", RUBY_METHOD_FUNC(caliculate_func), 1);
}
```

- Makefileを生成します. ```extconf.rb```を作成して```ruby extconf.rb```します.

```rb
# extconf.rb
require 'mkmf'
create_makefile('distance')
```

- Makefileが生成されたら, ```make```します.
```distance.o```, ```distance.bundle```というファイルが生成されていると思います. 

- コーパスをベクトルに変換したファイル```vectors.bin```を用意して下さい.
- 今回はDemo用のコーパスを仕様しています. 


これで準備完了です. 

- requireしてみる

```rb
require './distance'

distance = Distance.new
distance.open
distance.caliculate("emacs")
#=> "vim xemacs wysiwyg posix scripting debugging debuggers bash lisp extensible bcpl csh toolkit editors unix cvs gnu interpreter debugger ircii matlab compile programmer perl tcl kylix fortran ide gtk jre intercal frontend installer preprocessor lgpl glibc runtime postscript executable figlet usemodwiki cli rexx awk gdb edlin gcc authoring assembler tex"
```
できた！

- 注意

日本語を扱う時, distance.caliculate("str")した出力結果を

```rb
distance.caliculate("こんにちは").force_encoding("UTF-8")
```
とかする必要があります...

## ひとこと

* C拡張を書くの初めてだったり, 色々と問題点が山積みなのですが, 一応動かすまではできました. 
* 自分のアプリケーションを動かすために必要だった類語抽出だけ行えるようにしました.
* また, 文字列をUTF-8で,cos類似度も受け取れるように, 結果を配列で出力するよう作り直します.
* ファイル指定もまだできません. open_funcの最初の方の ```f = fopen("vectors.bin", "rb");// ファイル読み込み``` で都度名前を変える仕様になっています...

## 参考
[Ruby拡張ライブラリ作成チュートリアル](http://ruby.gfd-dennou.org/tutorial/ruby-ext/)