LoginSignup
10
12

More than 5 years have passed since last update.

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

Posted at

Rubyでword2vec使えたら良いのにということで

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

まずは, 準備です.

  • distance.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します.
# extconf.rb
require 'mkmf'
create_makefile('distance')
  • Makefileが生成されたら, makeします.
    distance.o, distance.bundleというファイルが生成されていると思います.

  • コーパスをベクトルに変換したファイルvectors.binを用意して下さい.

  • 今回はDemo用のコーパスを仕様しています.

これで準備完了です.

  • requireしてみる
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")した出力結果を

distance.caliculate("こんにちは").force_encoding("UTF-8")

とかする必要があります...

ひとこと

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

参考

Ruby拡張ライブラリ作成チュートリアル

10
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
12