ネコでもテキスト分類器のRubyライブラリが生成できる便利ツールを作った

あまり細かいことは気にせずテキスト分類器のRubyライブラリを1コマンドで自動生成する便利ツールを作りました。
いろいろ迷走している間に。

gem install nekoneko_gen

でインストールできます。

なにをするものなのか、ちょっと分かりにくいので、例で説明します。

2ちゃんねるの投稿からどのスレッドの投稿か判定するライブラリを生成する

例として、2ちゃんねるに投稿されたデータから、投稿(レス)がどのスレッドのレスか判定するライブラリを生成してみます。

準備

まず

gem install nekoneko_gen

でインストールします。
Ruby 1.8.7でも1.9.2でも動きますが1.9.2のほうが5倍くらい速いので1.9.2以降がおすすめです。
環境は、ここではUbuntuを想定しますが、Windowsでも使えます。(WindowsXP, ruby 1.9.3p0で確認)


データは僕が用意しているので、適当にdataというディレクトリを作ってダウンロードします。

% mkdir data
% cd data
% wget -i http://www.udp.jp/misc/2ch_data/
% cd ..

でダウンロードされます。

いろいろダウンロードされますが、とりあえず、ドラクエ質問スレとラブプラス質問スレの2択にしようと思うので、以下のファイルを使用します。
これらを使って、入力された文章がドラクエ質問スレのレスか、ラブプラス質問スレのレスか判定するライブラリを生成します。

dragon_quest.txt
ドラゴンクエストなんでも質問スレのデータ(約3万件)
dragon_quest_test.txt
dragon_quest.txtからテスト用に500件抜いたレス(dragon_quest.txtには含まれない)
dragon_quest_test2.txt
dragon_quest_test.txtの2レスを1行にしたデータ
loveplus.txt
ラブプラス質問スレのデータ(約2.5万件)
loveplus_test.txt
loveplus.txtからテスト用に500件抜いたレス
loveplus_test2.txt
loveplus_test.txtの2レスを1行にしたデータ

入力データのフォーマットは、1行1データです。このデータの場合は、1レス中の改行コードを消して1行1レスにしてしています。
データの整備はアンカー(>>1のようなリンク)を消しただけなので、「サンクス」「死ぬ」「そうです」みたいなどう考えても分類無理だろみたいなデータも含まれています。また突然荒らしが登場してスレと関係ないクソレスを繰り返していたりもします。
*_test.txtと*_test2.txtは生成されたライブラリの確認用です。*_test.txtのうちいくつ正解できるか数えるのに使います。*_test2.txtは、*_test.txtの2レスを1データにしたものです。2ちゃんの投稿は短すぎてうまく判定できないことが多いのでは? と思うので、なら2レスあれば判定できるのか? という確認用です。

生成してみる
% nekoneko_gen -n game_thread_classifier data/dragon_quest.txt data/loveplus.txt

nekoneko_genというコマンドで生成します。
-nで生成する分類器の名前を指定します。これは".rb"を付けてファイル名になるのと、キャピタライズしてモジュール名になります。生成先ディレクトリを指定したい場合は、直接ファイル名でも指定できます。
その後ろに分類(判定)したい種類ごとに学習用のファイルを指定します。最低2ファイルで、それ以上ならいくつでも指定できます。

ちょっと時間がかかるので、待ちます。2分くらい。

% nekoneko_gen -n game_thread_classifier data/dragon_quest.txt data/loveplus.txt
loading data/dragon_quest.txt... 35.5426s
loading data/loveplus.txt... 36.0522s
step   0... 0.879858, 3.7805s
step   1... 0.919624, 2.2018s
step   2... 0.932147, 2.1174s
step   3... 0.940959, 2.0569s
step   4... 0.946985, 1.8876s
step   5... 0.950891, 1.8564s
step   6... 0.953541, 1.8398s
step   7... 0.955464, 1.8204s
step   8... 0.957427, 1.8008s
step   9... 0.959056, 1.7912s
step  10... 0.961098, 1.8027s
step  11... 0.961745, 1.7716s
step  12... 0.962943, 1.7633s
step  13... 0.963610, 1.7477s
step  14... 0.964611, 1.6216s
step  15... 0.965259, 1.7291s
step  16... 0.965730, 1.7271s
step  17... 0.966613, 1.7225s
step  18... 0.967241, 1.5861s
step  19... 0.967712, 1.7113s
DRAGON_QUEST, LOVEPLUS : 71573 features
done nyan!

終わったら -nで指定した名前のファイルにRubyのコードが生成されています。

% ls -la
...
-rw-r--r--  1 ore users 2555555 2012-05-28 08:10 game_thread_classifier.rb
...

2.5MBくらいありますね。結構デカい。
このファイルには、GameThreadClassifier(指定した名前をキャピタライズしたもの)というModuleが定義されていて、self.predict(text)というメソッドを持っています。このメソッドに文字列を渡すと、予測結果としてGameThreadClassifier::DRAGON_QUESTかGameThreadClassifier::LOVEPLUSを返します。この定数名は、コマンドに指定したデータファイル名を大文字にしたものです。

試してみる

生成されたライブラリを使ってみましょう。
注意として、Ruby 1.8.7の場合は、$KCODEを'u'にしておかないと動きません。あと入力の文字コードutf-8のみです。

# coding: utf-8
if (RUBY_VERSION < '1.9.0')
  $KCODE = 'u'
end
require './game_thread_classifier'

$stdout.sync = true
loop do
  print "> "
  line = $stdin.readline
  label = GameThreadClassifier.predict(line)
  puts "#{GameThreadClassifier::LABELS[label]}の話題です!!!"
end

こんなコードを console.rb として作ります。
GameThreadClassifier.predictは予測されるクラスのラベル番号を返します。
GameThreadClassifier::LABELSには、ラベル番号に対応するラベル名が入っているので、これを表示してみます。

% ruby console.rb
> 彼女からメールが来た
LOVEPLUSの話題です!!!
> 日曜日はデートしてました
LOVEPLUSの話題です!!!
> 金欲しい
DRAGON_QUESTの話題です!!!
> 王様になりたい
DRAGON_QUESTの話題です!!!
> スライム
DRAGON_QUESTの話題です!!!
> スライムを彼女にプレゼント
LOVEPLUSの話題です!!!
>

できてるっぽいですね。CTRL+DとかCTRL+Cとかで適当に終わります。

正解率を調べてみる

*_test.txt、*_test2.txtの何%くらい正解できるか調べてみます。

if (RUBY_VERSION < '1.9.0')
  $KCODE = 'u'
end
require './game_thread_classifier'

labels = Array.new(GameThreadClassifier.k, 0)
file = ARGV.shift
File.open(file) do |f|
  until f.eof?
    l = f.readline.chomp
    label = GameThreadClassifier.predict(l)
    labels[label] += 1
  end
end
count = labels.reduce(:+)
labels.each_with_index do |c, i|
  printf "%16s: %f\n", GameThreadClassifier::LABELS[i], c.to_f / count.to_f
end

引数に指定したファイルを1行ずつpredictに渡して、予測されたラベル番号の数を数えて、クラスごとに全体の何割かを表示するだけのコードです。
GameThreadClassifier.kは、クラス数(この場合、DRAGON_QUESTとLOVEPLUSで2)を返します。

% ruby test.rb data/dragon_quest_test.txt
    DRAGON_QUEST: 0.932000
        LOVEPLUS: 0.068000

data/dragon_quest_test.txtには、ドラクエ質問スレのデータしかないので、すべて正解であれば、DRAGON_QUEST: 1.0になるはずです。
DRAGON_QUEST: 0.932000なので、93.2%は正解して、6.8%はラブプラスと間違えたことが分かります。
同じようにすべて試してみましょう。

% ruby test.rb data/dragon_quest_test.txt
    DRAGON_QUEST: 0.932000
        LOVEPLUS: 0.068000
% ruby test.rb data/loveplus_test.txt
    DRAGON_QUEST: 0.124000
        LOVEPLUS: 0.876000
%
% ruby test.rb data/dragon_quest_test2.txt
    DRAGON_QUEST: 0.988000
        LOVEPLUS: 0.012000
% ruby test.rb data/loveplus_test2.txt
    DRAGON_QUEST: 0.012048
        LOVEPLUS: 0.987952

ラブプラスはちょっと悪くて、87%くらいですね。平均すると、90%くらい正解しています。
また2レスで判定すると98%以上正解することが分かりました。2レスあれば、それがドラクエスレか、ラブプラススレか、ほとんど間違えることなく判定できるっぽいですね。

まとめ

ここまで読んでいただければ、どういうものか分かったと思います。
用意したデータファイルを学習して、指定した文字列がどのデータファイルのデータと似ているか判定するRubyライブラリを生成します。
生成されたライブラリは、Rubyの標準ライブラリ以外では、 json と bimyou_segmenter に依存しています。

gem install json bimyou_segmenter

C Extensionが使えない環境だと、

gem install json_pure bimyou_segmenter

とすれば、いろんな環境で生成したライブラリが使えるようになります。
ちなみに bimyou_segmenter という名前からしてあやしげなライブラリは、これと似たような方法で自動生成した日本語分かち書きのライブラリです。

もっと試す!!

データは他に skyrim.txt (スカイリムの質問スレ)、mhf.txt (モンスターハンターフロンティアオンラインの質問スレ)を用意しているので、これらも学習できます。

% nekoneko_gen -n game_thread_classifier data/dragon_quest.txt data/loveplus.txt data/skyrim.txt data/mhf.txt

単純に指定するファイルを増やすだけです。
生成されるコードも判定結果が増えただけなので、上で作ったconsole.rb、test.rbがそのまま使えます。

% nekoneko_gen -n game_thread_classifier data/dragon_quest.txt data/loveplus.txt data/skyrim.txt data/mhf.txt
loading data/dragon_quest.txt... 35.4695s
loading data/loveplus.txt... 36.5006s
loading data/skyrim.txt... 148.8504s
loading data/mhf.txt... 94.2842s
step   0... 0.885344, 29.5712s
step   1... 0.918844, 24.0811s
step   2... 0.927274, 22.0760s
step   3... 0.932804, 20.7306s
step   4... 0.936590, 20.4044s
step   5... 0.939495, 19.2658s
step   6... 0.942164, 19.1920s
step   7... 0.943754, 19.1084s
step   8... 0.945903, 18.9361s
step   9... 0.948293, 18.8840s
step  10... 0.949483, 18.1423s
step  11... 0.950827, 18.6365s
step  12... 0.951693, 18.2945s
step  13... 0.952915, 18.0946s
step  14... 0.953600, 17.9010s
step  15... 0.954284, 17.8173s
step  16... 0.955062, 17.7265s
step  17... 0.956281, 17.0873s
step  18... 0.956424, 17.5843s
step  19... 0.957648, 17.5608s
DRAGON_QUEST : 181402 features
LOVEPLUS : 171552 features
SKYRIM : 199655 features
MHF : 194066 features
done nyan!

% ruby test.rb data/dragon_quest_test.txt
    DRAGON_QUEST: 0.862000
        LOVEPLUS: 0.042000
          SKYRIM: 0.056000
             MHF: 0.040000
% ruby test.rb data/loveplus_test.txt
    DRAGON_QUEST: 0.068000
        LOVEPLUS: 0.836000
          SKYRIM: 0.052000
             MHF: 0.044000
% ruby test.rb data/skyrim_test.txt
    DRAGON_QUEST: 0.044000
        LOVEPLUS: 0.040000
          SKYRIM: 0.844000
             MHF: 0.072000
% ruby test.rb data/mhf_test.txt
    DRAGON_QUEST: 0.052000
        LOVEPLUS: 0.024000
          SKYRIM: 0.058000
             MHF: 0.866000
%
% ruby test.rb data/dragon_quest_test2.txt
    DRAGON_QUEST: 0.964000
        LOVEPLUS: 0.016000
          SKYRIM: 0.012000
             MHF: 0.008000
% ruby test.rb data/loveplus_test2.txt
    DRAGON_QUEST: 0.004016
        LOVEPLUS: 0.987952
          SKYRIM: 0.008032
             MHF: 0.000000
% ruby test.rb data/skyrim_test2.txt
    DRAGON_QUEST: 0.000000
        LOVEPLUS: 0.020000
          SKYRIM: 0.964000
             MHF: 0.016000
% ruby test.rb data/mhf_test2.txt
    DRAGON_QUEST: 0.008032
        LOVEPLUS: 0.000000
          SKYRIM: 0.016064
             MHF: 0.975904

1レスの場合は、選択肢が増えた分悪くなっています。平均すると正解は85%くらいでしょうか。2レスの場合は、まだ97%以上正解しています。


簡単すぎワロタ(自作自演)

なぜこんなものを作ったのか

前の『句読点のない文字列を文単位に区切る』を作ったときにLIBLINEARを使ってて、LIBLINEARは速いし簡単なので、ネコでもわかるLIBLINEARみたいな感じでぜひ紹介したいと思って、その例としてテキスト分類をあげようと思ったのですが、MeCabやKyotoCabinetをインストールしてさまざまな作業スクリプトを書いてLIBLINEARで使えるアルゴリズムの違いやパラメータの意味について理解する必要があったりして……こんなんネコに分かるわけない…にゃん…と思った。
それで、1コマンドで使えて、インターフェースだけ知っていれば中身を知る必要がないブラックボックス的に使えるジェネレーターで依存関係の少ないライブラリコードが生成できれば、ネコでもちょっとしたテキスト分類器が作れるし、便利なのでは? と思ったのでした。

実装は、まず分かち書きのライブラリをTinySegmenter: Javascriptだけで実装されたコンパクトな分かち書きソフトウェアを参考に青空文庫のデータで学習しました。それを使って文章の特徴ベクトルとしてBag of wordsを作って、その特徴ベクトルを [機械学習] AROWのコードを書いてみた - tsubosakaの日記で紹介されているAROWという学習アルゴリズムを多クラスにしたもので学習して、学習されたモデルをRubyのコードテンプレートに埋め込んでいるだけです。いろいろ適当につなげただけなので、これ自体は特に面白いところはないと思いますけど、こういったツールの 便 利 さ の 可 能 性 みたいなものは伝わったのでは、と思います。

ちなみに分かち書きのライブラリからpure rubyで書いてあるので、そのへんをちょっと移植してテンプレートを作れば、JavaScriptPHPなど他の言語のコードも生成できます。今はできませんが。

最後に

それはともかくとして、LIBLINEARは速いし簡単なのでオススメしたい。
nekoneko_gen でも使っている bimyou_segmenter はLIBLINEARの学習結果(Model)からRubyライブラリを生成するプログラムで生成しているので、今度その話を書けたらと思います。
結局書くので nekoneko_gen とは一体なんだったのかと、今になって考えています。

キャプチャつなぎ職人になる方法

本来何と呼ぶ者なのか知らないので、知っている方がいたら教えてほしいのですが。
2ちゃんねるなどを見ていると、アニメのスクロールシーンの画面キャプチャを謎の技術でつなげている人がいます。

たとえば、次の4つの画像
http://38b8c019c6a8fb3561611ea811e70d25.gazou.me/large.png
http://5b509a1f84152ac49342f726d9583a66.gazou.me/large.png
http://ed30db536d833b2301c5ab18f25290a8.gazou.me/large.png
http://5b0de8f48210f04f5ccd3f0489d4a65e.gazou.me/large.png
(http://www.nicovideo.jp/watch/1333619854 より)
(最後の頭のシーンは口が閉じていたけど口は空いていたほうがいいと思ったので、口より上のみ切り出した)

これらをきれいにつなげて
http://a127ef747e022f337c0ff520853c0ca2.gazou.me/large.png
こうする。

方法を知っていれば簡単なのですが、たまにがんばって手でつなげたような画像を張る人がいるので、簡単な方法を紹介しておきます。

Microsoft ICEによるスティッチング

Microsoft ICE という Microsoft Research が開発したアニメのスクロールシーンをきれいにつなげるソフトを使うだけです。

以下のURLからダウンロードしてインストールします。
http://research.microsoft.com/en-us/um/redmond/groups/ivm/ice/


英語ですが使い方は簡単で、適当な間隔で保存したキャプチャ画像をまとめてドラック&ドロップするだけです。
http://191b54d2466affc9e9512549163d8c8b.gazou.me/large.png

あとは、Exportで保存画像の設定をして、Export to disk ボタンをクリックすると保存されます。
画像が汚いのはエコノミー画質のキャプチャだからで、きれいなキャプチャからだとそこそこきれいな画像ができます。

感想

個人的にはFlash2期がよかったですね。

クライアントサイドの巨大なクッキーとサーバーサイドのヘッダーサイズ制限の組み合わせによるDoSについて

もう10年以上前のネタなのですが、いまだに有効だし、最近、セッションを扱っていないならXSSがあってもあまり問題ないという意見を見ることがあるので、サービス提供側として案外面倒なことになる場合がある、という話を書きました。

POCです。
http://www.udp.jp/misc/largecookiedos.html

内容としては、

  1. JavaScriptから巨大なCookieをブラウザに設定できる
  2. HTTPサーバーは受け取れるHTTPヘッダーサイズの上限を持っていて、それを超えていた場合にBad Requestを返す
  3. 1によって2を超えるサイズのCookieが設定可能な場合がある(たぶんほとんどの場合可能)
  4. よってXSSなどによって巨大なCookieを設定されると以降サービスが利用できなくなる

というものです。
Cookieの有効期限を何十年も設定されるとユーザー側で勝手に回復することは期待できないので、なんらか対応が必要になると思います。

昔は、CGIチャットに来る掲示板荒らしがこういうスクリプトを張り付けて利用者を追い出したりしていました。
XSSはつらい問題だと思います。

家宅捜索されることもあるらしいので気を付けましょう。

2ちゃんねる Q&A 検索のその後

http://qarc.info/
のその後の日記です。

変更したこと

ドメインを変えた

ドメインは、前はkako.feezch.infoでしたが、Q&Aなのにkakoとかフィードを配信していないのにfeezch.infoのドメインとか意味不明な感じだったので変えました。qarc.infoです。これまたあまり意味のある名前ではないのですが、何か名前が必要だと思って、Q&Aスレをアーカイブするからqarcみたいな名前です。なぜsearchじゃないのかというと、arcのほうが響きがかっこいいからです。

質問スレ以外の過去ログを消した

公開時は、一部の板で1000に達したスレの過去ログを全部保存した上で、その中から質問スレを選んで、さらに質問文を選んで……としていましたが、レスのレコードが増すぎてやばい感じだったのと、全体の過去ログとか別にうちで持たなくてもいいやと思ったので消しました。
一度取り始めるとやめるのは勿体無い気がしてきますが、僕は●ユーザーなので過去ログはURLさえ分かればいつでも取れるし、また必要になったときに取ればいいです。
保存していたのは、今後質問レス抽出の精度が上がれば、質問スレ以外からも抽出しようと思っていたからですが、もう消したので、精度はあまり気にならなくなりました。

全文検索をちょっとマシにした
僕の魔界を救って→僕まか
モンスターハンターフロンティア→MHF

みたいな略語の辞書を作って、略されてても全文検索でヒットするようにしました。ただ、手動なのでつらいというか、ぜんぜん対応出来てない。Googleはこういうのがすごくよく出来ているので、Googleに慣れている人は平気で適当なクエリを投げてきますが、そんなの30分くらいで作ったうちの全文検索システムで出来るわけないです。
ただこのへんをきちんと作らないとトップページのQ&A検索を自信を持って勧められないので、全文検索をまともにするのが今後の主な課題になると思います。

XMLサイトマップを作ってクローラー目的の一覧を消した

公開時はQ&Aの一覧が新着順・人気順などありましたが、もともとクローラーがQ&Aごとのページをインデックスできるような意味が大きかったし、ログが増えて人間が見るのは不可能な量になっていたので、クローラー用のXMLサイトマップを作るようにして一覧を消しました。

アフィをいろいろアレした

省略します。

アクセス数

http://f5f3506df624bc426d789d5c0811abde.gazou.me/large.png
2/10に公開してからのGoogle Analyticsによる訪問者数(セッション数)のグラフです。
Google様の気分しだいで上がったり下がったりします。
今は大体一日12000UU、18000PVくらいです。

滞在時間はかなり短いのですが、質問文で検索すると2ちゃんねるで過去に行われた質問と回答のページが出てきてそれを見ると一発で答えが分かる(または分からないことが分かる)というのが基本的なモデルなので、短くていいと思います。

サーバー

SaaSesのOsukini Server GT 1台にクローラーからウェブ、DBまで何もかも入れています。検索と過去ログ一覧以外はページキャッシュしてnginxで配信しているだけなので、負荷はほとんどありません。
クローラーもfeezchのようなスレの差分を集めて回るタイプではなく、スレッド一覧から質問スレが落ちたのを検出して丸ごと取って終わりなので、あまり負荷はありません。
当分はこれ1台でいけるのではと思っています。

まとめ

サイトはシンプルになったと思います。
サイトのシステムは全自動で動いているので、僕はたまに思いついたことを実装して反映して、あとは結果を観測しているだけという状態です。さまざまな反応がゆっくりと進んでいる感じなので、早く時間がたって、どうなったか見たいと思っています。

2ちゃんねるに寄せられた質問と回答から検索できるページを作った

2ちゃんねる Q&A 検索 - QARC

人類の知の資産であるところの2ちゃんねるの過去ログから、特に有用と思われる質問と回答のやりとりを高度な自然言語処理技術を用いて抽出・アーカイブし、それらを誰もが自由に検索できるページを作りました。
僕は、feezch.infoという2ちゃんねるのパートスレを次スレまで次々に自動追跡しながらひとつのストリームとしてフィードを吐くウェブサービスを運営しているのですが、フィードの利用者がとても少ないわりに、過去ログへのアクセスがものすごくあって、そのほとんどは質問スレの回答求めて訪れる方のようなので、それなら一発で回答にアクセスできるページを作れば非常に大きな社会貢献になるのではないか、feezch.infoのコードを使えば簡単に作れるし、ビッグデータや。そう考えてこのサービスを作ることにしました。

コレを使うと、たとえば、「skyrim 水銀のインゴット」と入力して検索ボタンを押すと…
http://2bd642aadabd8fdc27fb3bd56a192efb.gazou.me/large.png

このように過去に2ちゃんねるに寄せられた質問レスが出てきます。
「回答を見る」をクリックすると……

http://1e14e14a1c3f2fc208bd1ed8f7b94289.gazou.me/large.png

知の資産に一発アクセス!

全文検索はpg_trgmで適当に作ったもので、短いクエリのパフォーマンスが最悪なのでなので、どちらかというとGoogle様に期待しています。

それと2ちゃんねるのレスを文に分割するライブラリを作った

さて、全自動2chまとめブログの話はこれくらいにして、技術的な話。

質問文の抽出はいろいろがんばっているのですが、正直なところ「?が含まれるかどうか??」のほうが精度がいいのでは???ってレベルです。
そのため質問スレ以外はゴミを大量に作りそうで処理してないのですが、今後精度がよくなれば持っている過去ログ全てから質問のみを抽出するといったこともやっていきたいと思っています。

で、少しはがんばった結果、ちょっと便利なライブラリができたのでここで紹介だけしておきます。

2ちゃんねるのレスは、たとえば、

wryebashについて調べていたら、「MODのインスコとアンインスコでゴミを残す」
というのが出てきたのでそれについて調べてみたのですが
他のサイトで同じようなバグは報告されていませんでした
実際に使っている方は問題なく使えていますか?
また、他のバグや不満な点などを教えてもらえると有りがたいです。
NMMから乗り換えようと思っているのでお願いします。

「氷の上の血」のクエストが発生しないんで困ってるんだけど
なんとか、コンソールとかで強制的に発生できないかな?
wiki見る限り、経過日数が250日経過してるとアウトっぽいんで
今のデータだとほぼ詰みっぽいんで、家が買えない・・・・

古びたお守り:龍の護石から回避距離6達人10のお守りが出たのですが
風化したお守りからは同等もしくはそれ以上の数値を持つお守りが出ることはあるのでしょうか
それとも古おまのみにしか出ないお守りなのでしょうか
誰か持っている人いたら回答よろしくお願いします
風おまを狙って凍土へ行くか古おま、光おまも狙って火山へ行くか…
早く下山したい…

のような「。」があったりなかったり、「。」の代わりに改行が使われていたり、しかも「。」とは関係なく文字列の幅を調節するために改行していたりで、どこまでがひとつの文なのか分からなくなっています。
文章をbag of wordsで処理する分には問題ならないと思いますけど、係り受け解析までしたいときに文の区切りが分からないとうまく処理できないとか、そこまでしなくても、たとえば疑問文かどうかは文末を見れば大体分かりますが、文末がどこか分からないと見ることができません。
おい、困ったぞ。
そこで、これをこういった文字列を自動で文に区切るライブラリを作りました。(Rubyですが、MeCabがあるところならきっと簡単に移植できます)

句読点のない文字列を文単位に区切る — Gist

こんな感じになります。(別途MeCab-Rubyが必要です)

require 'zch_sentence'
res1 = <<T
wryebashについて調べていたら、「MODのインスコとアンインスコでゴミを残す」
というのが出てきたのでそれについて調べてみたのですが
他のサイトで同じようなバグは報告されていませんでした
実際に使っている方は問題なく使えていますか?
また、他のバグや不満な点などを教えてもらえると有りがたいです。
NMMから乗り換えようと思っているのでお願いします。
T
res2 = <<T
「氷の上の血」のクエストが発生しないんで困ってるんだけど
なんとか、コンソールとかで強制的に発生できないかな?
wiki見る限り、経過日数が250日経過してるとアウトっぽいんで
今のデータだとほぼ詰みっぽいんで、家が買えない・・・・ 
T
res3 = <<T
古びたお守り:龍の護石から回避距離6達人10のお守りが出たのですが
風化したお守りからは同等もしくはそれ以上の数値を持つお守りが出ることはあるのでしょうか
それとも古おまのみにしか出ないお守りなのでしょうか
誰か持っている人いたら回答よろしくお願いします
風おまを狙って凍土へ行くか古おま、光おまも狙って火山へ行くか…
早く下山したい…
T
puts "---"
puts ZchSentence.emend(res1)
puts "---"
puts ZchSentence.emend(res2)
puts "---"
puts ZchSentence.emend(res3)

出力

    • -

wryebashについて調べていたら、「MODのインスコとアンインスコでゴミを残す」というのが出てきたのでそれについて調べてみたのですが、他のサイトで同じようなバグは報告されていませんでした。
実際に使っている方は問題なく使えていますか?
また、他のバグや不満な点などを教えてもらえると有りがたいです。
NMMから乗り換えようと思っているのでお願いします。

    • -

「氷の上の血」のクエストが発生しないんで困ってるんだけど、なんとか、コンソールとかで強制的に発生できないかな?
wiki見る限り、経過日数が250日経過してるとアウトっぽいんで、今のデータだとほぼ詰みっぽいんで、家が買えない・・・・

    • -

古びたお守り:龍の護石から回避距離6達人10のお守りが出たのですが、風化したお守りからは同等もしくはそれ以上の数値を持つお守りが出ることはあるのでしょうか。
それとも古おまのみにしか出ないお守りなのでしょうか。
誰か持っている人いたら回答よろしくお願いします。
風おまを狙って凍土へ行くか古おま、光おまも狙って火山へ行くか…
早く下山したい…

適切に「。」や「、」を補完し、1行1文にして返してくれます。便利。

アルゴリズムとしては、まず「改行の位置は文の区切りまたは文節の区切りである」という仮定を置いた上で、

  • 「。」や「?」「!」などで終わっていたら文の区切りでしょう
  • 「、」で終わっていたら文の区切りではないでしょう
  • 行がアスキーコードの範囲のみで構成されているなら、きっとそれはURLであり1行でひとつの単位でしょう
  • 連続して改行されていたら(空行を挟んでいたら)その上はきっと文の終わりでしょう

などなどのルールを適用しようとして、それらのルールで最後まで対応できない場合は、あらかじめ学習している機械学習器で文として区切れるか区切れないかを判定しています。

この分類器はあまり精度がよくないのですが、ルールのおかげであまり使うこともないので、大体はうまくいくという感じのものです。
この部分はいろいろ妥協していたり、もっとうまくやる方法があると思うけど、僕が詳しくないために試せていないとか、もっと自然言語処理やってますみたいな人が作って僕に無料で使わせるべきだ、みたいに思っている部分があるので、今度またどうやって学習しているのかなどの話を詳しく書こうと思っています。

ちなみにZchSentence.splitという関数を使うと機械学習器だけを使って区切ります。
(。や?や改行が含まれないデータで学習しているので、あらかじめ消しておきます)

puts "---"
puts ZchSentence.split(res1.gsub(/[。、?\n]/,"")).join("。\n") + "。\n"
puts "---"
puts ZchSentence.split(res2.gsub(/[。、?\n]/,"")).join("。\n") + "。\n"
puts "---"
puts ZchSentence.split(res3.gsub(/[。、?\n]/,"")).join("。\n") + "。\n"
    • -

wryebashについて調べていたら「MODのインスコとアンインスコでゴミを残す。
」というのが出てきたのでそれについて調べてみたのですが他のサイトで同じようなバグは報告されていませんでした。
実際に使っている方は問題なく使えていますか。
また他のバグや不満な点などを教えてもらえると有りがたいです。
NMMから乗り換えようと思っているのでお願いします。

    • -

「氷の上の血」のクエストが発生しないんで困ってるんだけどなんとかコンソールとかで強制的に発生できないかな。
wiki見る限り経過日数が250日経過してるとアウトっぽいんで今のデータだとほぼ詰み。
っぽいんで家が買えない。
・・・・。

    • -

古びたお守り:龍の護石から回避距離6達人10のお守りが出たのですが風化した。
お守りからは同等もしくはそれ以上の数値を持つお守りが出ることはあるのでしょうか。
それとも古おまのみにしか出ない。
お守りなのでしょうか。
誰か持っている。
人いたら回答よろしくお願いします。
風おまを狙って凍土へ行くか。
古おま光。
おまも狙って火山へ行くか。
…早く下山したい…。

たったこれだけの文字列でも結構間違えていますね。
学習データが青空文庫のテキストなので、そのせいもあるかもしれませんけど、とにかくあまりよくないです。最悪です。

OpenMPとウェブアプリケーションの組み合わせについて

PerlMagick が OpenMP 有効だと高負荷になる件 :: drk7jp

を見て、僕も自分が書いているライブラリで、OpenMPを使いまくっていて、将来的に同じことを言われる不安があるので、うまく付き合うためのノウハウを書いておきます。

まずOpenMPとは何かというと、並列コンピューティング環境を利用するために用いられる標準化された基盤です(Wikipediaより)。これを使うとC言語のfor文を並列化したりが簡単にできるので、マルチコアのCPUを使っている場合に、うまくいけばコア数倍の速度で処理できるようになります。1000msかかってた処理が4コアのCPUだと250msで処理できるようになった! やったね! というわけ。

しかし、gccVC++でのOpenMPの並列化for文はデフォルトではプロセス毎にコアを全部使って処理するので、これをマルチプロセスと組み合わせると、プロセスごとにコア数分のスレッドが生成されるため、システム全体では、OSから見えるプロセッサ数以上のスレッドが生成されて各プロセスが処理を取り合うことになり逆に性能が落ちます。
こういう場合でもうまく動かすために、環境変数OMP_NUM_THREADSというものがあって、この変数にスレッド数を設定することでOpenMPが生成するデフォルトのスレッド数を変更できます。なので、

同時に立ち上がるプロセス数 * OMP_NUM_THREADS = OSから見えるプロセッサ数(普通はCPUのコア数)

とすれば最高の性能が出ると思います。設定方法はサーバーソフトウェアによって違うので書きませんが、プロセス数の制限と環境変数を設定する方法は大体あると思います。(またこれは単純な数値計算だけの場合で、長いI/Oが入る一般的な処理を含む場合はOSから見えるプロセッサ数より多いほうが性能が出る場合があります)

これを前提として、目的によって、同時に立ち上がるプロセス数を多く取るか、OMP_NUM_THREADSを多く取るかを選ぶことになります。

たとえば、アクセスは少ないけどすごく重い処理をするので、できるだけ速くレスポンスを返したい場合は、同時に立ち上がるプロセス数を1にしてOMP_NUM_THREADSをCPUのコア数と同じにします。これが、1リクエストについて1番短い時間でレスポンスを返せる設定です。ただ、これだと1リクエストずつ順番に処理することになるので、同時に何人かアクセスする環境では、待ち時間が長い人が出てきます。そういう場合は、同時に立ち上がるプロセス数を増やして、その分OMP_NUM_THREADSを減らします。プロセス数が2の場合は、OMP_NUM_THREADSが1/2になり、同時に2つのリクエストを処理できるようになる代わりに、1リクエストあたりにかかる時間が2倍になる可能性があります。OMP_NUM_THREADS=1がOpenMPが無効なのと同じ状態です(ただ、並列化の処理を分けて書いている場合はオーバーヘッドがあるので、ずっと1にする気なら無効にしたほうがいいです)。

ウェブはたくさんの人がアクセスするものですから、ほとんどの場合は無効でいいと思いますが、スーパークソ重い処理をするので、できるだけ速くレスポンスを返したくて、プロセス数でなくハードウェアの台数で並列化できる場合は、OpenMPを有効にしてプロセス数少なめでスレッド数と台数を増やしたほうが速く処理できてうれしいと思います。

ちょっと面倒ですけど、ただ飯の時代は終わった。

追記

OpenMP3.0だとOMP_THREAD_LIMITという環境変数があるらしく、使える場合はこっちを使ったほうが適切です。OMP_NUM_THREADSはプログラム中でスレッド数を指定している場合はそっちの優先度が高いので制限できない。
あと自分のライブラリだとなぜか環境変数の設定が効かなくて、oreore_procs()で並列数を決めるようにして、この中で自分で環境変数を読んだりしているので、なにか別の問題があるのかもしれないです。