nekoneko_gen 0.3.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +472 -51
- data/lib/nekoneko_gen.rb +1 -1
- data/lib/nekoneko_gen/arow.rb +7 -6
- data/lib/nekoneko_gen/classifier.rb +1 -1
- data/lib/nekoneko_gen/classifier_factory.rb +4 -4
- data/lib/nekoneko_gen/linear_classifier.rb +6 -23
- data/lib/nekoneko_gen/mlp.rb +15 -15
- data/lib/nekoneko_gen/pa.rb +5 -4
- data/lib/nekoneko_gen/text_classifier_generator.rb +73 -10
- data/lib/nekoneko_gen/version.rb +1 -1
- data/test/nekoneko_gen_test.rb +3 -1
- metadata +8 -8
data/README.md
CHANGED
@@ -1,75 +1,126 @@
|
|
1
1
|
# NekonekoGen
|
2
2
|
|
3
|
-
|
3
|
+
ネコでもテキスト分類器のRubyライブラリが生成できる便利ツール
|
4
4
|
|
5
|
-
##
|
5
|
+
## インストール
|
6
6
|
|
7
|
-
|
7
|
+
% gem install nekoneko_gen
|
8
|
+
|
9
|
+
でインストールできます。
|
10
|
+
|
11
|
+
または Gemfile に次の行を追加して
|
8
12
|
|
9
13
|
gem 'nekoneko_gen'
|
10
14
|
|
11
|
-
|
15
|
+
次のコマンドを実行。
|
12
16
|
|
13
17
|
$ bundle
|
14
18
|
|
15
|
-
|
19
|
+
## 使い方の例 (1) 2ちゃんねるの投稿からどのスレッドの投稿か判定するライブラリを生成する
|
20
|
+
|
21
|
+
例として、2ちゃんねるに投稿されたデータから、投稿(レス)がどのスレッドのレスか判定するライブラリを生成してみます。
|
22
|
+
|
23
|
+
まず
|
16
24
|
|
17
|
-
|
25
|
+
% gem install nekoneko_gen
|
18
26
|
|
19
|
-
|
27
|
+
でインストールします。
|
28
|
+
Ruby 1.8.7でも1.9.2でも動きますが1.9.2のほうが5倍くらい速いので1.9.2以降がおすすめです。
|
29
|
+
環境は、ここではUbuntuを想定しますが、Windowsでも使えます。(WindowsXP, ruby 1.9.3p0で確認)
|
30
|
+
|
31
|
+
データは僕が用意しているので、適当にdataというディレクトリを作ってダウンロードします。
|
20
32
|
|
21
33
|
% mkdir data
|
22
34
|
% cd data
|
23
35
|
% wget -i http://www.udp.jp/misc/2ch_data/index1.txt
|
24
|
-
...
|
25
36
|
% cd ..
|
37
|
+
|
38
|
+
でダウンロードされます。
|
39
|
+
|
40
|
+
いろいろダウンロードされますが、とりあえず、ドラクエ質問スレとラブプラス質問スレの2択にしようと思うので、以下のファイルを使用します。
|
41
|
+
これらを使って、入力された文章がドラクエ質問スレのレスか、ラブプラス質問スレのレスか判定するライブラリを生成します。
|
42
|
+
|
43
|
+
- dragon_quest.txt: ドラゴンクエストなんでも質問スレのデータ(約3万件)
|
44
|
+
- dragon_quest_test.txt: dragon_quest.txtからテスト用に500件抜いたレス(dragon_quest.txtには含まれない)
|
45
|
+
- dragon_quest_test2.txt: dragon_quest_test.txtの2レスを1行にしたデータ
|
46
|
+
- loveplus.txt: ラブプラス質問スレのデータ(約2.5万件)
|
47
|
+
- loveplus_test.txt: loveplus.txtからテスト用に500件抜いたレス
|
48
|
+
- loveplus_test2.txt: loveplus_test.txtの2レスを1行にしたデータ
|
49
|
+
|
50
|
+
入力データのフォーマットは、1カテゴリ1ファイル1行1データです。このデータの場合は、1レス中の改行コードを消して1行1レスにしてしています。
|
51
|
+
データの整備はアンカー(>>1のようなリンク)を消しただけなので、「サンクス」「死ぬ」「そうです」みたいなどう考えても分類無理だろみたいなデータも含まれています。また突然荒らしが登場してスレと関係ないクソレスを繰り返していたりもします。
|
52
|
+
\*_test.txtと\*_test2.txtは生成されたライブラリの確認用です。*_test.txtのうちいくつ正解できるか数えるのに使います。*_test2.txtは、*_test.txtの2レスを1データにしたものです。2ちゃんの投稿は短すぎてうまく判定できないことが多いのでは? と思うので、なら2レスあれば判定できるのか? という確認用です。
|
53
|
+
|
54
|
+
### 生成してみる
|
55
|
+
|
56
|
+
% nekoneko_gen -n game_thread_classifier data/dragon_quest.txt data/loveplus.txt
|
57
|
+
|
58
|
+
nekoneko_genというコマンドで生成します。
|
59
|
+
-nで生成する分類器の名前を指定します。これは".rb"を付けてファイル名になるのと、キャピタライズしてモジュール名になります。生成先ディレクトリを指定したい場合は、直接ファイル名でも指定できます。
|
60
|
+
その後ろに分類(判定)したい種類ごとに学習用のファイルを指定します。最低2ファイルで、それ以上ならいくつでも指定できます。
|
61
|
+
|
62
|
+
ちょっと時間がかかるので、待ちます。2分くらい。
|
63
|
+
|
26
64
|
% nekoneko_gen -n game_thread_classifier data/dragon_quest.txt data/loveplus.txt
|
27
|
-
loading data/dragon_quest.txt...
|
28
|
-
loading data/loveplus.txt...
|
29
|
-
step 0... 0.
|
30
|
-
step 1... 0.
|
31
|
-
step 2... 0.
|
32
|
-
step 3... 0.
|
33
|
-
step 4... 0.
|
34
|
-
step 5... 0.
|
35
|
-
step 6... 0.
|
36
|
-
step 7... 0.
|
37
|
-
step 8... 0.
|
38
|
-
step 9... 0.
|
39
|
-
step 10... 0.
|
40
|
-
step 11... 0.
|
41
|
-
step 12... 0.
|
42
|
-
step 13... 0.
|
43
|
-
step 14... 0.
|
44
|
-
step 15... 0.
|
45
|
-
step 16... 0.
|
46
|
-
step 17... 0.
|
47
|
-
step 18... 0.
|
48
|
-
step 19... 0.
|
49
|
-
DRAGON_QUEST, LOVEPLUS :
|
65
|
+
loading data/dragon_quest.txt... 37.0108s
|
66
|
+
loading data/loveplus.txt... 37.5334s
|
67
|
+
step 0... 0.893258, 4.2150s
|
68
|
+
step 1... 0.936877, 1.8508s
|
69
|
+
step 2... 0.948048, 1.3891s
|
70
|
+
step 3... 0.954943, 1.3921s
|
71
|
+
step 4... 0.959396, 1.1686s
|
72
|
+
step 5... 0.962824, 1.3013s
|
73
|
+
step 6... 0.964833, 1.1754s
|
74
|
+
step 7... 0.966271, 1.1562s
|
75
|
+
step 8... 0.967749, 1.2547s
|
76
|
+
step 9... 0.968537, 1.1301s
|
77
|
+
step 10... 0.969581, 1.1238s
|
78
|
+
step 11... 0.970074, 1.2611s
|
79
|
+
step 12... 0.970369, 1.1102s
|
80
|
+
step 13... 0.971197, 0.9888s
|
81
|
+
step 14... 0.972162, 1.2344s
|
82
|
+
step 15... 0.972655, 1.0946s
|
83
|
+
step 16... 0.973186, 1.0937s
|
84
|
+
step 17... 0.973482, 1.1007s
|
85
|
+
step 18... 0.973896, 1.0846s
|
86
|
+
step 19... 0.973975, 1.0803s
|
87
|
+
DRAGON_QUEST, LOVEPLUS : 86497 features
|
50
88
|
done nyan!
|
51
|
-
|
89
|
+
|
90
|
+
終わったら -nで指定した名前のファイルにRubyのコードが生成されています。
|
91
|
+
|
52
92
|
% ls -la
|
53
93
|
...
|
54
|
-
-rw-r--r--
|
94
|
+
-rw-r--r-- 1 ore users 5000504 2012-06-04 07:20 game_thread_classifier.rb
|
55
95
|
...
|
56
|
-
|
57
|
-
|
96
|
+
|
97
|
+
5MBくらいありますね。結構デカい。
|
98
|
+
このファイルには、GameThreadClassifier(指定した名前をキャピタライズしたもの)というModuleが定義されていて、self.predict(text)というメソッドを持っています。このメソッドに文字列を渡すと、予測結果としてGameThreadClassifier::DRAGON_QUESTかGameThreadClassifier::LOVEPLUSを返します。この定数名は、コマンドに指定したデータファイル名を大文字にしたものです。
|
99
|
+
|
100
|
+
### 試してみる
|
101
|
+
|
102
|
+
生成されたライブラリを使ってみましょう。
|
103
|
+
注意として、Ruby 1.8.7の場合は、$KCODEを'u'にしておかないと動きません。あと入力の文字コードもutf-8のみです。
|
104
|
+
|
58
105
|
# coding: utf-8
|
59
106
|
if (RUBY_VERSION < '1.9.0')
|
60
107
|
$KCODE = 'u'
|
61
108
|
end
|
62
109
|
require './game_thread_classifier'
|
110
|
+
require 'kconv'
|
63
111
|
|
64
112
|
$stdout.sync = true
|
65
113
|
loop do
|
66
114
|
print "> "
|
67
|
-
line = $stdin.readline
|
115
|
+
line = $stdin.readline.toutf8
|
68
116
|
label = GameThreadClassifier.predict(line)
|
69
117
|
puts "#{GameThreadClassifier::LABELS[label]}の話題です!!!"
|
70
118
|
end
|
71
|
-
|
72
|
-
|
119
|
+
|
120
|
+
こんなコードを console.rb として作ります。
|
121
|
+
GameThreadClassifier.predictは予測されるクラスのラベル番号を返します。
|
122
|
+
GameThreadClassifier::LABELSには、ラベル番号に対応するラベル名が入っているので、これを表示してみます。
|
123
|
+
|
73
124
|
% ruby console.rb
|
74
125
|
> 彼女からメールが来た
|
75
126
|
LOVEPLUSの話題です!!!
|
@@ -83,8 +134,13 @@ Or install it yourself as:
|
|
83
134
|
DRAGON_QUESTの話題です!!!
|
84
135
|
> スライムを彼女にプレゼント
|
85
136
|
LOVEPLUSの話題です!!!
|
86
|
-
|
87
|
-
|
137
|
+
|
138
|
+
できてるっぽいですね。CTRL+DとかCTRL+Cとかで適当に終わります。
|
139
|
+
|
140
|
+
### 正解率を調べてみる
|
141
|
+
|
142
|
+
\*_test.txt、\*_test2.txtの何%くらい正解できるか調べてみます。
|
143
|
+
|
88
144
|
if (RUBY_VERSION < '1.9.0')
|
89
145
|
$KCODE = 'u'
|
90
146
|
end
|
@@ -103,24 +159,389 @@ Or install it yourself as:
|
|
103
159
|
labels.each_with_index do |c, i|
|
104
160
|
printf "%16s: %f\n", GameThreadClassifier::LABELS[i], c.to_f / count.to_f
|
105
161
|
end
|
106
|
-
|
162
|
+
|
163
|
+
引数に指定したファイルを1行ずつpredictに渡して、予測されたラベル番号の数を数えて、クラスごとに全体の何割かを表示するだけのコードです。
|
164
|
+
GameThreadClassifier.kは、クラス数(この場合、DRAGON_QUESTとLOVEPLUSで2)を返します。
|
165
|
+
|
166
|
+
% ruby test.rb data/dragon_quest_test.txt
|
167
|
+
DRAGON_QUEST: 0.924000
|
168
|
+
LOVEPLUS: 0.076000
|
169
|
+
|
170
|
+
data/dragon_quest_test.txtには、ドラクエ質問スレのデータしかないので、すべて正解であれば、DRAGON_QUEST: 1.0になるはずです。
|
171
|
+
DRAGON_QUEST: 0.924000なので、92.4%は正解して、7.6%はラブプラスと間違えたことが分かります。
|
172
|
+
同じようにすべて試してみましょう。
|
107
173
|
|
108
174
|
% ruby test.rb data/dragon_quest_test.txt
|
109
|
-
DRAGON_QUEST: 0.
|
110
|
-
LOVEPLUS: 0.
|
175
|
+
DRAGON_QUEST: 0.924000
|
176
|
+
LOVEPLUS: 0.076000
|
111
177
|
% ruby test.rb data/loveplus_test.txt
|
112
|
-
DRAGON_QUEST: 0.
|
113
|
-
LOVEPLUS: 0.
|
178
|
+
DRAGON_QUEST: 0.102000
|
179
|
+
LOVEPLUS: 0.898000
|
180
|
+
|
114
181
|
% ruby test.rb data/dragon_quest_test2.txt
|
115
182
|
DRAGON_QUEST: 0.988000
|
116
183
|
LOVEPLUS: 0.012000
|
117
184
|
% ruby test.rb data/loveplus_test2.txt
|
118
|
-
DRAGON_QUEST: 0.
|
119
|
-
LOVEPLUS: 0.
|
185
|
+
DRAGON_QUEST: 0.004016
|
186
|
+
LOVEPLUS: 0.995984
|
187
|
+
|
188
|
+
ラブプラスはちょっと悪くて、89.8%くらいですね。平均すると、91%くらい正解しています。
|
189
|
+
また2レスで判定すると99%以上正解することが分かりました。2レスあれば、それがドラクエスレか、ラブプラススレか、ほとんど間違えることなく判定できるっぽいですね。
|
190
|
+
|
191
|
+
#### まとめ
|
192
|
+
|
193
|
+
ここまで読んでいただければ、どういうものか分かったと思います。
|
194
|
+
用意したデータファイルを学習して、指定した文字列がどのデータファイルのデータと似ているか判定するRubyライブラリを生成します。
|
195
|
+
生成されたライブラリは、Rubyの標準ライブラリ以外では、 json と bimyou_segmenter に依存しています。
|
196
|
+
|
197
|
+
gem install json bimyou_segmenter
|
198
|
+
|
199
|
+
C Extensionが使えない環境だと、
|
200
|
+
|
201
|
+
gem install json_pure bimyou_segmenter
|
202
|
+
|
203
|
+
とすれば、いろんな環境で生成したライブラリが使えるようになります。
|
204
|
+
|
205
|
+
### 他のファイルも試す
|
206
|
+
|
207
|
+
データは他に skyrim.txt (スカイリムの質問スレ)、mhf.txt (モンスターハンターフロンティアオンラインの質問スレ)を用意しているので、これらも学習できます。
|
208
|
+
|
209
|
+
% nekoneko_gen -n game_thread_classifier data/dragon_quest.txt data/loveplus.txt data/skyrim.txt data/mhf.txt
|
210
|
+
|
211
|
+
単純に指定するファイルを増やすだけです。
|
212
|
+
生成されるコードも判定結果が増えただけなので、上で作ったconsole.rb、test.rbがそのまま使えます。
|
213
|
+
|
214
|
+
% nekoneko_gen -n game_thread_classifier data/dragon_quest.txt data/loveplus.txt data/skyrim.txt data/mhf.txt
|
215
|
+
loading data/dragon_quest.txt... 37.1598s
|
216
|
+
loading data/loveplus.txt... 37.9838s
|
217
|
+
loading data/skyrim.txt... 134.5455s
|
218
|
+
loading data/mhf.txt... 72.3003s
|
219
|
+
step 0... 0.882245, 19.6765s
|
220
|
+
step 1... 0.922662, 14.9239s
|
221
|
+
step 2... 0.932979, 14.5471s
|
222
|
+
step 3... 0.939081, 13.0942s
|
223
|
+
step 4... 0.943442, 12.2289s
|
224
|
+
step 5... 0.947011, 12.7141s
|
225
|
+
step 6... 0.950062, 12.0611s
|
226
|
+
step 7... 0.952911, 11.9480s
|
227
|
+
step 8... 0.955120, 11.3372s
|
228
|
+
step 9... 0.956726, 11.8161s
|
229
|
+
step 10... 0.958260, 11.1741s
|
230
|
+
step 11... 0.959807, 11.1724s
|
231
|
+
step 12... 0.960831, 11.6116s
|
232
|
+
step 13... 0.961533, 11.0797s
|
233
|
+
step 14... 0.962678, 10.4930s
|
234
|
+
step 15... 0.963860, 11.4895s
|
235
|
+
step 16... 0.964193, 10.9576s
|
236
|
+
step 17... 0.965106, 11.4999s
|
237
|
+
step 18... 0.965567, 10.2368s
|
238
|
+
step 19... 0.966096, 10.8386s
|
239
|
+
DRAGON_QUEST : 245796 features
|
240
|
+
LOVEPLUS : 245796 features
|
241
|
+
SKYRIM : 245796 features
|
242
|
+
MHF : 245796 features
|
243
|
+
done nyan!
|
120
244
|
|
245
|
+
% ruby test.rb data/dragon_quest_test.txt
|
246
|
+
DRAGON_QUEST: 0.864000
|
247
|
+
LOVEPLUS: 0.040000
|
248
|
+
SKYRIM: 0.062000
|
249
|
+
MHF: 0.034000
|
250
|
+
% ruby test.rb data/loveplus_test.txt
|
251
|
+
DRAGON_QUEST: 0.070000
|
252
|
+
LOVEPLUS: 0.832000
|
253
|
+
SKYRIM: 0.056000
|
254
|
+
MHF: 0.042000
|
255
|
+
% ruby test.rb data/skyrim_test.txt
|
256
|
+
DRAGON_QUEST: 0.046000
|
257
|
+
LOVEPLUS: 0.038000
|
258
|
+
SKYRIM: 0.860000
|
259
|
+
MHF: 0.056000
|
260
|
+
% ruby test.rb data/mhf_test.txt
|
261
|
+
DRAGON_QUEST: 0.042000
|
262
|
+
LOVEPLUS: 0.022000
|
263
|
+
SKYRIM: 0.056000
|
264
|
+
MHF: 0.880000
|
121
265
|
|
122
|
-
%
|
123
|
-
|
124
|
-
|
125
|
-
|
266
|
+
% ruby test.rb data/dragon_quest_test2.txt
|
267
|
+
DRAGON_QUEST: 0.968000
|
268
|
+
LOVEPLUS: 0.012000
|
269
|
+
SKYRIM: 0.008000
|
270
|
+
MHF: 0.012000
|
271
|
+
% ruby test.rb data/loveplus_test2.txt
|
272
|
+
DRAGON_QUEST: 0.000000
|
273
|
+
LOVEPLUS: 0.991968
|
274
|
+
SKYRIM: 0.008032
|
275
|
+
MHF: 0.000000
|
276
|
+
% ruby test.rb data/skyrim_test2.txt
|
277
|
+
DRAGON_QUEST: 0.004000
|
278
|
+
LOVEPLUS: 0.008000
|
279
|
+
SKYRIM: 0.976000
|
280
|
+
MHF: 0.012000
|
281
|
+
% ruby test.rb data/mhf_test2.txt
|
282
|
+
DRAGON_QUEST: 0.008032
|
283
|
+
LOVEPLUS: 0.000000
|
284
|
+
SKYRIM: 0.012048
|
285
|
+
MHF: 0.979920
|
286
|
+
|
287
|
+
1レスの場合は、選択肢が増えた分悪くなっています。平均すると正解は86%くらいでしょうか。2レスの場合は、まだ97%以上正解しています。
|
288
|
+
|
289
|
+
|
290
|
+
## 使い方の例 (2) 20 newsgroupsを試してみる
|
291
|
+
|
292
|
+
|
293
|
+
文書分類では、20newsgroupsというデータセットがよく使われるようなので、試してみました。
|
294
|
+
nekoneko_genは英語テキストにも対応しています。
|
295
|
+
|
296
|
+
http://people.csail.mit.edu/jrennie/20Newsgroups/
|
297
|
+
|
298
|
+
これは20種類のニュースグループに投稿された約2万件のドキュメントを含むデータセットです(学習用が1.1万件、確認用が7.5千件だった)。
|
299
|
+
ニュースグループというのは、メーリングリストで2ちゃんねるをやっている感じのものだと思います。
|
300
|
+
20種類の板に投稿されたレスをどの板の投稿か判定するマシンを学習します。
|
301
|
+
|
302
|
+
|
303
|
+
(注意: ここに書かれている作業用のコードはRuby1.9系でしか動きません)
|
304
|
+
|
305
|
+
### 最新のnekoneko_genにアップデート
|
306
|
+
|
307
|
+
まず
|
308
|
+
|
309
|
+
% gem update nekoneko_gen
|
310
|
+
|
311
|
+
とアップデートします。(古いものは英語に対応していないかもしれません)
|
312
|
+
これを書いている時点の最新は0.4.1です。
|
313
|
+
入っていない場合は、
|
314
|
+
|
315
|
+
% gem install nekoneko_gen
|
316
|
+
|
317
|
+
でインストールされます。
|
318
|
+
|
319
|
+
### データの準備
|
320
|
+
|
321
|
+
サイトを見ると何種類かありますけど、20news-bydate.tar.gz を使います。
|
322
|
+
|
323
|
+
% wget http://people.csail.mit.edu/jrennie/20Newsgroups/20news-bydate.tar.gz
|
324
|
+
% tar -xzvf 20news-bydate.tar.gz
|
325
|
+
% ls
|
326
|
+
20news-bydate-test 20news-bydate-train
|
327
|
+
|
328
|
+
train用とtest用に分かれているらしいので、trainで学習して、testで確認します。
|
329
|
+
構造を見てみましょう。
|
330
|
+
|
331
|
+
|
332
|
+
% ls 20news-bydate-train
|
333
|
+
alt.atheism comp.os.ms-windows.misc comp.sys.mac.hardware misc.forsale rec.motorcycles rec.sport.hockey sci.electronics sci.space talk.politics.guns talk.politics.misc
|
334
|
+
comp.graphics comp.sys.ibm.pc.hardware comp.windows.x rec.autos rec.sport.baseball sci.crypt sci.med soc.religion.christian talk.politics.mideast talk.religion.misc
|
335
|
+
% ls 20news-bydate-train/comp.os.ms-windows.misc
|
336
|
+
10000 9141 9159 9450 9468 9486 9506
|
337
|
+
|
338
|
+
20news-bydate-trainと20news-bydate-testの下に各カテゴリのディレクトリがあって、各カテゴリのディレクトリにドキュメントがファイルに分かれて入っているようです。
|
339
|
+
|
340
|
+
nekoneko_genは、1ファイル1カテゴリ1行1データの入力フォーマットなので、まずこんなスクリプトで変換します。
|
341
|
+
|
342
|
+
# coding: utf-8
|
343
|
+
# 20news-conv.rb
|
344
|
+
require 'fileutils'
|
345
|
+
require 'kconv'
|
346
|
+
|
347
|
+
src = ARGV.shift
|
348
|
+
dest = ARGV.shift
|
349
|
+
unless (src && dest)
|
350
|
+
warn "20news-conv.rb srcdir destdir\n"
|
351
|
+
exit(-1)
|
352
|
+
end
|
353
|
+
|
354
|
+
FileUtils.mkdir_p(dest)
|
355
|
+
data = Hash.new
|
356
|
+
# 元データの各ファイルについて
|
357
|
+
Dir.glob("#{src}/*/*").each do |file|
|
358
|
+
if (File.file?(file))
|
359
|
+
# root/category/nに分解
|
360
|
+
root, category, n = file.split('/')[-3 .. -1]
|
361
|
+
if (root && category && n)
|
362
|
+
data[category] ||= []
|
363
|
+
# ファイルの内容を改行をスペースに置き換えて(1行にして)カテゴリのデータに追加
|
364
|
+
data[category] << NKF::nkf("-w", File.read(file)).gsub(/[\r\n]+/, ' ')
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
# 出力側で
|
370
|
+
data.each do |k,v|
|
371
|
+
# カテゴリ名.txtのファイルにデータを行単位で吐く
|
372
|
+
path = File.join(dest, "#{k}.txt")
|
373
|
+
File.open(path, "w") do |f|
|
374
|
+
f.write v.join("\n")
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
train、testというディレクトリに変換。
|
379
|
+
|
380
|
+
% ruby 20news-conv.rb 20news-bydate-train train
|
381
|
+
% ruby 20news-conv.rb 20news-bydate-test test
|
382
|
+
%
|
383
|
+
% ls test
|
384
|
+
alt.atheism.txt comp.sys.ibm.pc.hardware.txt misc.forsale.txt rec.sport.baseball.txt sci.electronics.txt soc.religion.christian.txt talk.politics.misc.txt
|
385
|
+
comp.graphics.txt comp.sys.mac.hardware.txt rec.autos.txt rec.sport.hockey.txt sci.med.txt talk.politics.guns.txt talk.religion.misc.txt
|
386
|
+
comp.os.ms-windows.misc.txt comp.windows.x.txt rec.motorcycles.txt sci.crypt.txt sci.space.txt talk.politics.mideast.txt
|
387
|
+
% head alt.atheism.txt
|
388
|
+
|
389
|
+
できてます。
|
390
|
+
|
391
|
+
### 学習
|
392
|
+
|
393
|
+
1コマンドです。ここまでの作業のことは忘れましょう。分類器の名前はnews20にしました。
|
394
|
+
trainの下を全部指定します。
|
395
|
+
|
396
|
+
% nekoneko_gen -n news20 train/*
|
397
|
+
|
398
|
+
ちょっと時間かかります。
|
399
|
+
|
400
|
+
% nekoneko_gen -n news20 train/*
|
401
|
+
loading train/alt.atheism.txt... 11.2039s
|
402
|
+
loading train/comp.graphics.txt... 10.0659s
|
403
|
+
loading train/comp.os.ms-windows.misc.txt... 24.7611s
|
404
|
+
loading train/comp.sys.ibm.pc.hardware.txt... 9.1767s
|
405
|
+
loading train/comp.sys.mac.hardware.txt... 8.3413s
|
406
|
+
loading train/comp.windows.x.txt... 13.9806s
|
407
|
+
loading train/misc.forsale.txt... 6.8255s
|
408
|
+
loading train/rec.autos.txt... 9.9041s
|
409
|
+
loading train/rec.motorcycles.txt... 9.4798s
|
410
|
+
loading train/rec.sport.baseball.txt... 9.9481s
|
411
|
+
loading train/rec.sport.hockey.txt... 14.2056s
|
412
|
+
loading train/sci.crypt.txt... 19.5707s
|
413
|
+
loading train/sci.electronics.txt... 9.6204s
|
414
|
+
loading train/sci.med.txt... 13.6632s
|
415
|
+
loading train/sci.space.txt... 14.4867s
|
416
|
+
loading train/soc.religion.christian.txt... 16.4918s
|
417
|
+
loading train/talk.politics.guns.txt... 16.2433s
|
418
|
+
loading train/talk.politics.mideast.txt... 21.8133s
|
419
|
+
loading train/talk.politics.misc.txt... 16.2976s
|
420
|
+
loading train/talk.religion.misc.txt... 10.4111s
|
421
|
+
step 0... 0.953548, 58.1906s
|
422
|
+
step 1... 0.970537, 47.1082s
|
423
|
+
step 2... 0.980550, 41.9248s
|
424
|
+
step 3... 0.985889, 37.6781s
|
425
|
+
step 4... 0.989483, 35.0408s
|
426
|
+
step 5... 0.991824, 33.8357s
|
427
|
+
step 6... 0.993727, 30.1385s
|
428
|
+
step 7... 0.995139, 29.5926s
|
429
|
+
step 8... 0.996107, 29.3976s
|
430
|
+
step 9... 0.997182, 27.9107s
|
431
|
+
step 10... 0.997546, 27.1800s
|
432
|
+
step 11... 0.998004, 26.4783s
|
433
|
+
step 12... 0.998581, 26.7023s
|
434
|
+
step 13... 0.998985, 25.9511s
|
435
|
+
step 14... 0.999145, 24.7697s
|
436
|
+
step 15... 0.999324, 24.7991s
|
437
|
+
step 16... 0.999430, 24.8879s
|
438
|
+
step 17... 0.999569, 24.7246s
|
439
|
+
step 18... 0.999622, 25.2175s
|
440
|
+
step 19... 0.999615, 23.5225s
|
441
|
+
ALT_ATHEISM : 153334 features
|
442
|
+
COMP_GRAPHICS : 153334 features
|
443
|
+
COMP_OS_MS_WINDOWS_MISC : 153334 features
|
444
|
+
COMP_SYS_IBM_PC_HARDWARE : 153334 features
|
445
|
+
COMP_SYS_MAC_HARDWARE : 153334 features
|
446
|
+
COMP_WINDOWS_X : 153334 features
|
447
|
+
MISC_FORSALE : 153334 features
|
448
|
+
REC_AUTOS : 153334 features
|
449
|
+
REC_MOTORCYCLES : 153334 features
|
450
|
+
REC_SPORT_BASEBALL : 153334 features
|
451
|
+
REC_SPORT_HOCKEY : 153334 features
|
452
|
+
SCI_CRYPT : 153334 features
|
453
|
+
SCI_ELECTRONICS : 153334 features
|
454
|
+
SCI_MED : 153334 features
|
455
|
+
SCI_SPACE : 153334 features
|
456
|
+
SOC_RELIGION_CHRISTIAN : 153334 features
|
457
|
+
TALK_POLITICS_GUNS : 153334 features
|
458
|
+
TALK_POLITICS_MIDEAST : 153334 features
|
459
|
+
TALK_POLITICS_MISC : 153334 features
|
460
|
+
TALK_RELIGION_MISC : 153334 features
|
461
|
+
done nyan!
|
462
|
+
|
463
|
+
終わったらnews20.rbというRubyのライブラリが生成されています。
|
464
|
+
|
465
|
+
% ls -la news20.rb
|
466
|
+
-rw-r--r-- 1 ore users 66599221 2012-06-02 17:10 news20.rb
|
467
|
+
|
468
|
+
60MB以上あります。デカい。
|
469
|
+
|
470
|
+
### 確認
|
471
|
+
|
472
|
+
20カテゴリもあって前回のスクリプトで1カテゴリずつ見るのはきついので、一気に確認するスクリプトを書きました。
|
473
|
+
|
474
|
+
|
475
|
+
# coding: utf-8
|
476
|
+
# test.rb
|
477
|
+
|
478
|
+
# 分類器を読み込む
|
479
|
+
require './news20'
|
480
|
+
|
481
|
+
# ファイル名をnekoneko_genが返すラベル名に変換する関数
|
482
|
+
def label_name(file)
|
483
|
+
File.basename(file, ".txt").gsub(/[\.\-]/, "_").upcase
|
484
|
+
end
|
485
|
+
|
486
|
+
count = 0
|
487
|
+
correct = 0
|
488
|
+
# 指定された各ファイルについて
|
489
|
+
ARGV.each do |file|
|
490
|
+
# ファイル名からラベル名を得る
|
491
|
+
name = label_name(file)
|
492
|
+
|
493
|
+
# ラベル名から正解ラベル(定数)に変換
|
494
|
+
# (News20::LABELSにラベル番号順のラベル名があるので添え字位置を探す)
|
495
|
+
correct_label = News20::LABELS.each_with_index.select{|v,i| v == name}.flatten.pop
|
496
|
+
|
497
|
+
file_count = 0
|
498
|
+
file_correct = 0
|
499
|
+
# ファイルの各行データについて
|
500
|
+
File.read(file).lines do |l|
|
501
|
+
# 予測
|
502
|
+
label = News20.predict(l)
|
503
|
+
# ラベルが一致していたら
|
504
|
+
if (label == correct_label)
|
505
|
+
# 正解!!
|
506
|
+
file_correct += 1
|
507
|
+
end
|
508
|
+
# データ数
|
509
|
+
file_count += 1
|
510
|
+
end
|
511
|
+
correct += file_correct
|
512
|
+
count += file_count
|
513
|
+
# ファイルの内での正解率を表示
|
514
|
+
printf("%26s: %f\n", name, file_correct.to_f / file_count.to_f)
|
515
|
+
end
|
516
|
+
|
517
|
+
# 全体の正解率を表示
|
518
|
+
printf("\nAccuracy: %f\n", correct.to_f / count.to_f)
|
519
|
+
|
520
|
+
testの下を全部指定します。
|
521
|
+
|
522
|
+
% ruby test.rb test/*
|
523
|
+
ALT_ATHEISM: 0.789969
|
524
|
+
COMP_GRAPHICS: 0.825193
|
525
|
+
COMP_OS_MS_WINDOWS_MISC: 0.753807
|
526
|
+
COMP_SYS_IBM_PC_HARDWARE: 0.778061
|
527
|
+
COMP_SYS_MAC_HARDWARE: 0.867532
|
528
|
+
COMP_WINDOWS_X: 0.815190
|
529
|
+
MISC_FORSALE: 0.902564
|
530
|
+
REC_AUTOS: 0.916667
|
531
|
+
REC_MOTORCYCLES: 0.969849
|
532
|
+
REC_SPORT_BASEBALL: 0.957179
|
533
|
+
REC_SPORT_HOCKEY: 0.984962
|
534
|
+
SCI_CRYPT: 0.952020
|
535
|
+
SCI_ELECTRONICS: 0.778626
|
536
|
+
SCI_MED: 0.881313
|
537
|
+
SCI_SPACE: 0.936548
|
538
|
+
SOC_RELIGION_CHRISTIAN: 0.937186
|
539
|
+
TALK_POLITICS_GUNS: 0.934066
|
540
|
+
TALK_POLITICS_MIDEAST: 0.914894
|
541
|
+
TALK_POLITICS_MISC: 0.616129
|
542
|
+
TALK_RELIGION_MISC: 0.677291
|
543
|
+
|
544
|
+
Accuracy: 0.866171
|
545
|
+
|
546
|
+
86.6%でした。
|
126
547
|
|
data/lib/nekoneko_gen.rb
CHANGED
@@ -41,7 +41,7 @@ module NekonekoGen
|
|
41
41
|
return -1
|
42
42
|
end
|
43
43
|
end
|
44
|
-
o.on('-p C', "parameter (default AROW::R=
|
44
|
+
o.on('-p C', "parameter (default AROW::R=10.0, PA2::C=1.0, MLP::HIDDEN_UNIT=K)") do |v|
|
45
45
|
c = v.to_f
|
46
46
|
end
|
47
47
|
o.on('-q', "quiet") do
|
data/lib/nekoneko_gen/arow.rb
CHANGED
@@ -4,10 +4,10 @@ require File.expand_path(File.join(File.dirname(__FILE__), 'linear_classifier'))
|
|
4
4
|
module NekonekoGen
|
5
5
|
# Adaptive Regularization of Weight Vector
|
6
6
|
class Arow < LinearClassifier
|
7
|
-
R =
|
7
|
+
R = 10.0
|
8
8
|
DEFAULT_ITERATION = 20
|
9
9
|
|
10
|
-
def initialize(k, options = {})
|
10
|
+
def initialize(k, n, options = {})
|
11
11
|
@r = options[:c] || R
|
12
12
|
@k = k
|
13
13
|
@cov = []
|
@@ -15,14 +15,14 @@ module NekonekoGen
|
|
15
15
|
@w = []
|
16
16
|
@bias = []
|
17
17
|
if (@k == 2)
|
18
|
-
@cov[0] =
|
19
|
-
@w[0] =
|
18
|
+
@cov[0] = Array.new(n, 1.0)
|
19
|
+
@w[0] = Array.new(n, 0.0)
|
20
20
|
@covb[0] = 1.0
|
21
21
|
@bias[0] = 0.0
|
22
22
|
else
|
23
23
|
k.times do |i|
|
24
|
-
@cov[i] =
|
25
|
-
@w[i] =
|
24
|
+
@cov[i] = Array.new(n, 1.0)
|
25
|
+
@w[i] = Array.new(n, 0.0)
|
26
26
|
@covb[i] = 1.0
|
27
27
|
@bias[i] = 0.0
|
28
28
|
end
|
@@ -33,6 +33,7 @@ module NekonekoGen
|
|
33
33
|
cov = @cov[i]
|
34
34
|
covb = @covb[i]
|
35
35
|
bias = @bias[i]
|
36
|
+
|
36
37
|
y = label == i ? 1 : -1
|
37
38
|
score = bias + dot(vec, w)
|
38
39
|
alpha = 1.0 - y * score
|
@@ -5,15 +5,15 @@ require File.expand_path(File.join(File.dirname(__FILE__), 'mlp'))
|
|
5
5
|
|
6
6
|
module NekonekoGen
|
7
7
|
module ClassifierFactory
|
8
|
-
def self.create(k, options)
|
8
|
+
def self.create(k, n, options)
|
9
9
|
method = options[:method] || :arow
|
10
10
|
case (method)
|
11
11
|
when :arow
|
12
|
-
Arow.new(k, options)
|
12
|
+
Arow.new(k, n, options)
|
13
13
|
when :pa, :pa1, :pa2
|
14
|
-
PA.new(k, options)
|
14
|
+
PA.new(k, n, options)
|
15
15
|
when :mlp
|
16
|
-
MLP.new(k, options)
|
16
|
+
MLP.new(k, n, options)
|
17
17
|
else
|
18
18
|
raise ArgumentError
|
19
19
|
end
|
@@ -14,19 +14,6 @@ module NekonekoGen
|
|
14
14
|
end
|
15
15
|
dot
|
16
16
|
end
|
17
|
-
def strip!
|
18
|
-
@w.each {|w|
|
19
|
-
w.reject!{|k,v|
|
20
|
-
if (v.abs < Float::EPSILON)
|
21
|
-
# p v
|
22
|
-
true
|
23
|
-
else
|
24
|
-
false
|
25
|
-
end
|
26
|
-
}
|
27
|
-
}
|
28
|
-
@w
|
29
|
-
end
|
30
17
|
def update(vec, label)
|
31
18
|
loss = 0.0
|
32
19
|
if (@k == 2)
|
@@ -46,20 +33,16 @@ module NekonekoGen
|
|
46
33
|
w[i].size
|
47
34
|
end
|
48
35
|
end
|
49
|
-
def parameter_code(lang
|
36
|
+
def parameter_code(lang = :ruby)
|
50
37
|
lang ||= :ruby
|
51
38
|
case lang
|
52
39
|
when :ruby
|
53
40
|
else
|
54
41
|
raise NotImplementedError
|
55
42
|
end
|
56
|
-
|
57
|
-
wvec = self.strip!.map {|w|
|
58
|
-
w.reduce({}) {|h, kv| h[index_converter.call(kv[0])] = kv[1]; h }
|
59
|
-
}
|
60
43
|
<<CODE
|
61
44
|
BIAS = #{self.bias.inspect}
|
62
|
-
W = JSON.load(#{
|
45
|
+
W = JSON.load(#{@w.to_json.inspect})
|
63
46
|
CODE
|
64
47
|
end
|
65
48
|
def classify_method_code(lang)
|
@@ -69,14 +52,14 @@ CODE
|
|
69
52
|
else
|
70
53
|
raise NotImplementedError
|
71
54
|
end
|
72
|
-
|
73
55
|
<<CODE
|
74
|
-
def self.classify(
|
56
|
+
def self.classify(svec)
|
75
57
|
if (K == 2)
|
76
|
-
|
58
|
+
w0 = W[0]
|
59
|
+
(BIAS[0] + svec.map{|k, v| v * w0[k]}.reduce(0.0, :+)) > 0.0 ? 0 : 1
|
77
60
|
else
|
78
61
|
W.each_with_index.map {|w, i|
|
79
|
-
[BIAS[i] +
|
62
|
+
[BIAS[i] + svec.map{|k, v| v * w[k]}.reduce(0.0, :+), i]
|
80
63
|
}.max.pop
|
81
64
|
end
|
82
65
|
end
|
data/lib/nekoneko_gen/mlp.rb
CHANGED
@@ -13,7 +13,7 @@ module NekonekoGen
|
|
13
13
|
def default_hidden_unit
|
14
14
|
@k
|
15
15
|
end
|
16
|
-
def initialize(k, options)
|
16
|
+
def initialize(k, n, options)
|
17
17
|
@k = k
|
18
18
|
@output_units = @k == 2 ? 1 : @k
|
19
19
|
@hidden_units = (options[:c] || default_hidden_unit).to_i
|
@@ -22,15 +22,18 @@ module NekonekoGen
|
|
22
22
|
@input_bias = []
|
23
23
|
@hidden_bias = []
|
24
24
|
@hidden_units.times do |i|
|
25
|
-
@input[i] =
|
26
|
-
|
25
|
+
input = @input[i] = []
|
26
|
+
n.times do |j|
|
27
|
+
input[j] = rand_value
|
28
|
+
end
|
29
|
+
@input_bias[i] = rand_value
|
27
30
|
end
|
28
31
|
@output_units.times do |i|
|
29
|
-
@hidden[i] = []
|
32
|
+
hidden = @hidden[i] = []
|
30
33
|
@hidden_units.times do |j|
|
31
|
-
|
34
|
+
hidden[j] = rand_value
|
32
35
|
end
|
33
|
-
@hidden_bias[i] =
|
36
|
+
@hidden_bias[i] = rand_value
|
34
37
|
end
|
35
38
|
end
|
36
39
|
def update(vec, label)
|
@@ -115,7 +118,7 @@ module NekonekoGen
|
|
115
118
|
def sigmoid(a)
|
116
119
|
1.0 / (1.0 + Math.exp(-a))
|
117
120
|
end
|
118
|
-
def
|
121
|
+
def rand_value
|
119
122
|
(rand - 0.5)
|
120
123
|
end
|
121
124
|
def noise
|
@@ -124,22 +127,18 @@ module NekonekoGen
|
|
124
127
|
def default_iteration
|
125
128
|
DEFAULT_ITERATION
|
126
129
|
end
|
127
|
-
def parameter_code(lang
|
130
|
+
def parameter_code(lang = :ruby)
|
128
131
|
lang ||= :ruby
|
129
132
|
case lang
|
130
133
|
when :ruby
|
131
134
|
else
|
132
135
|
raise NotImplementedError
|
133
136
|
end
|
134
|
-
|
135
|
-
wvec = @input.map {|w|
|
136
|
-
w.reduce({}) {|h, kv| h[index_converter.call(kv[0])] = kv[1]; h }
|
137
|
-
}
|
138
137
|
<<CODE
|
139
138
|
HIDDEN_UNITS = #{@hidden_units}
|
140
139
|
INPUT_BIAS = #{@input_bias.inspect}
|
141
140
|
HIDDEN_BIAS = #{@hidden_bias.inspect}
|
142
|
-
INPUT_W = JSON.load(#{
|
141
|
+
INPUT_W = JSON.load(#{@input.to_json.inspect})
|
143
142
|
HIDDEN_W = #{@hidden.inspect}
|
144
143
|
CODE
|
145
144
|
end
|
@@ -151,11 +150,12 @@ CODE
|
|
151
150
|
raise NotImplementedError
|
152
151
|
end
|
153
152
|
<<CODE
|
154
|
-
def self.classify(
|
153
|
+
def self.classify(svec)
|
155
154
|
input_y = []
|
156
155
|
HIDDEN_UNITS.times do |i|
|
156
|
+
w = INPUT_W[i]
|
157
157
|
input_y[i] = sigmoid(INPUT_BIAS[i] +
|
158
|
-
|
158
|
+
svec.map{|k,v| v * w[k]}.reduce(0.0, :+))
|
159
159
|
end
|
160
160
|
if (K == 2)
|
161
161
|
HIDDEN_BIAS[0] +
|
data/lib/nekoneko_gen/pa.rb
CHANGED
@@ -8,17 +8,17 @@ module NekonekoGen
|
|
8
8
|
NORM = 2.0 # norm + BIAS
|
9
9
|
DEFAULT_ITERATION = 20
|
10
10
|
|
11
|
-
def initialize(k, options = {})
|
11
|
+
def initialize(k, n, options = {})
|
12
12
|
@k = k
|
13
13
|
@c = options[:c] || C
|
14
14
|
@w = []
|
15
15
|
@bias = []
|
16
16
|
if (@k == 2)
|
17
|
-
@w[0] =
|
17
|
+
@w[0] = Array.new(n, 0.0)
|
18
18
|
@bias[0] = 0.0
|
19
19
|
else
|
20
20
|
k.times do |i|
|
21
|
-
@w[i] =
|
21
|
+
@w[i] = Array.new(n, 0.0)
|
22
22
|
@bias[i] = 0.0
|
23
23
|
end
|
24
24
|
end
|
@@ -39,7 +39,7 @@ module NekonekoGen
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
def pa2(y, l)
|
42
|
-
y * (l / NORM + 0.5 / @c)
|
42
|
+
y * (l / (NORM + 0.5 / @c))
|
43
43
|
end
|
44
44
|
def pa1(y, l)
|
45
45
|
y * [@c, (l / NORM)].min
|
@@ -50,6 +50,7 @@ module NekonekoGen
|
|
50
50
|
def update_at(i, vec, label)
|
51
51
|
y = label == i ? 1 : -1
|
52
52
|
w = @w[i]
|
53
|
+
|
53
54
|
score = @bias[i] + dot(vec, w)
|
54
55
|
l = 1.0 - score * y
|
55
56
|
if (l > 0.0)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
require 'json'
|
2
3
|
require 'nkf'
|
3
4
|
require 'bimyou_segmenter'
|
4
5
|
|
@@ -14,14 +15,16 @@ module NekonekoGen
|
|
14
15
|
@files = files
|
15
16
|
@word2id = {}
|
16
17
|
@id2word = {}
|
17
|
-
@classifier =
|
18
|
+
@classifier = nil
|
19
|
+
@k = files.size
|
18
20
|
@name = safe_name(@filename).split("_").map(&:capitalize).join
|
19
21
|
@labels = files.map {|file| "#{safe_name(file).upcase}"}
|
22
|
+
@idf = {}
|
20
23
|
end
|
21
24
|
def train(iteration = nil)
|
22
|
-
iteration ||= @classifier.default_iteration
|
23
25
|
data = []
|
24
|
-
|
26
|
+
word_count = Hash.new(0)
|
27
|
+
@k.times do |i|
|
25
28
|
t = Time.now
|
26
29
|
data[i] = []
|
27
30
|
print "loading #{@files[i]}... "
|
@@ -39,8 +42,36 @@ module NekonekoGen
|
|
39
42
|
end
|
40
43
|
puts sprintf("%.4fs", Time.now - t)
|
41
44
|
end
|
45
|
+
data_min = data.map{|v| v.size}.min
|
46
|
+
data.each do |cd|
|
47
|
+
w = data_min / cd.size.to_f
|
48
|
+
cd.each do |vec|
|
49
|
+
vec.keys.each do |k|
|
50
|
+
word_count[k] += w
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
document_count = data_min * data.size
|
55
|
+
@idf = Array.new(0, 0)
|
56
|
+
word_count.each{|k, freq|
|
57
|
+
@idf[k] = Math.log(document_count / freq) * MATH_LOG2_INV + 1.0
|
58
|
+
}
|
59
|
+
data.each do |cdata|
|
60
|
+
cdata.each do |vec|
|
61
|
+
if (vec.size >= 2)
|
62
|
+
r = 1.0 / (Math.log(vec.size) * MATH_LOG2_INV)
|
63
|
+
else
|
64
|
+
r = 1.0
|
65
|
+
end
|
66
|
+
vec.each do |k, freq|
|
67
|
+
vec[k] = Math.log(freq + 1.0) * MATH_LOG2_INV * r * @idf[k]
|
68
|
+
end
|
69
|
+
normalize(vec)
|
70
|
+
end
|
71
|
+
end
|
42
72
|
|
43
|
-
|
73
|
+
@classifier = ClassifierFactory.create(@k, @word2id.size, @options)
|
74
|
+
iteration ||= @classifier.default_iteration
|
44
75
|
iteration.times do |step|
|
45
76
|
loss = 0.0
|
46
77
|
c = 0
|
@@ -48,13 +79,14 @@ module NekonekoGen
|
|
48
79
|
print sprintf("step %3d...", step)
|
49
80
|
|
50
81
|
@classifier.k.times.map do |i|
|
51
|
-
sampling(data[i],
|
82
|
+
sampling(data[i], data_min).map {|vec| [vec, i] }
|
52
83
|
end.flatten(1).shuffle!.each do |v|
|
53
84
|
loss += @classifier.update(v[0], v[1])
|
54
85
|
c += 1
|
55
86
|
end
|
56
87
|
print sprintf(" %.6f, %.4fs\n", 1.0 - loss / c.to_f, Time.now - t)
|
57
88
|
end
|
89
|
+
|
58
90
|
if (@classifier.k > 2)
|
59
91
|
@classifier.k.times do |i|
|
60
92
|
puts "#{@labels[i]} : #{@classifier.features(i)} features"
|
@@ -95,11 +127,12 @@ class #{@name}
|
|
95
127
|
LABELS = #{@labels.inspect}
|
96
128
|
K = #{@classifier.k}
|
97
129
|
private
|
130
|
+
MATH_LOG2_INV = 1.0 / Math.log(2.0)
|
98
131
|
def self.fv(text)
|
99
132
|
prev = nil
|
100
|
-
BimyouSegmenter.segment(text,
|
101
|
-
|
102
|
-
|
133
|
+
svec = BimyouSegmenter.segment(text,
|
134
|
+
:white_space => true,
|
135
|
+
:symbol => true).map do |word|
|
103
136
|
if (prev)
|
104
137
|
if (NGRAM_TARGET =~ word)
|
105
138
|
nword = [prev + word, word]
|
@@ -115,14 +148,43 @@ class #{@name}
|
|
115
148
|
end
|
116
149
|
word
|
117
150
|
end
|
118
|
-
end.flatten
|
151
|
+
end.flatten.map{|word| WORD_INDEX[word]}.compact.reduce(Hash.new(0)) {|h,k| h[k] += 1; h }
|
152
|
+
unless (svec.empty?)
|
153
|
+
if (svec.size >= 2)
|
154
|
+
r = 1.0 / (Math.log(svec.size) * MATH_LOG2_INV)
|
155
|
+
else
|
156
|
+
r = 1.0
|
157
|
+
end
|
158
|
+
svec.each do |k, freq|
|
159
|
+
if (idf = IDF[k])
|
160
|
+
svec[k] = Math.log(freq + 1.0) * MATH_LOG2_INV * r * idf
|
161
|
+
else
|
162
|
+
svec[k] = 0.0
|
163
|
+
end
|
164
|
+
end
|
165
|
+
normalize(svec)
|
166
|
+
else
|
167
|
+
svec
|
168
|
+
end
|
169
|
+
end
|
170
|
+
def self.normalize(svec)
|
171
|
+
norm = Math.sqrt(svec.values.map{|v| v * v }.reduce(0.0, :+))
|
172
|
+
if (norm > 0.0)
|
173
|
+
s = 1.0 / norm
|
174
|
+
svec.each do |k, v|
|
175
|
+
svec[k] = v * s
|
176
|
+
end
|
177
|
+
end
|
178
|
+
svec
|
119
179
|
end
|
120
180
|
#{@classifier.classify_method_code(:ruby)}
|
121
181
|
NGRAM_TARGET = Regexp.new('(^[ァ-ヾ]+$)|(^[a-zA-Z\\-_a-zA-Z‐_0-90-9]+$)|' +
|
122
182
|
'(^[々〇ヵヶ' + [0x3400].pack('U') + '-' + [0x9FFF].pack('U') +
|
123
183
|
[0xF900].pack('U') + '-' + [0xFAFF].pack('U') +
|
124
184
|
[0x20000].pack('U') + '-' + [0x2FFFF].pack('U') + ']+$)')
|
125
|
-
#{@
|
185
|
+
IDF = JSON.load(#{@idf.to_json.inspect})
|
186
|
+
WORD_INDEX = JSON.load(#{@word2id.to_json.inspect})
|
187
|
+
#{@classifier.parameter_code(:ruby)}
|
126
188
|
end
|
127
189
|
MODEL
|
128
190
|
end
|
@@ -234,6 +296,7 @@ MODEL
|
|
234
296
|
Kernel.print s
|
235
297
|
end
|
236
298
|
end
|
299
|
+
MATH_LOG2_INV = 1.0 / Math.log(2.0)
|
237
300
|
SYMBOL = Regexp.new('^[^々〇' + [0x3400].pack('U') + '-' + [0x9FFF].pack('U') +
|
238
301
|
[0xF900].pack('U') + '-' + [0xFAFF].pack('U') +
|
239
302
|
[0x20000].pack('U') + '-' + [0x2FFFF].pack('U') +
|
data/lib/nekoneko_gen/version.rb
CHANGED
data/test/nekoneko_gen_test.rb
CHANGED
@@ -9,11 +9,13 @@ class NekonekoGenTest < Test::Unit::TestCase
|
|
9
9
|
@clean_files = []
|
10
10
|
end
|
11
11
|
def teardown
|
12
|
+
=begin
|
12
13
|
@clean_files.each do |file|
|
13
14
|
if (File.exist?(file))
|
14
15
|
File.unlink(file)
|
15
16
|
end
|
16
17
|
end
|
18
|
+
=end
|
17
19
|
end
|
18
20
|
|
19
21
|
def test_mlp
|
@@ -28,7 +30,7 @@ class NekonekoGenTest < Test::Unit::TestCase
|
|
28
30
|
gen2('arow', {:method => :arow})
|
29
31
|
gen3('arow',{:method => :arow})
|
30
32
|
end
|
31
|
-
|
33
|
+
|
32
34
|
def clean!(a, b)
|
33
35
|
if (File.exist?(a))
|
34
36
|
File.unlink(a)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nekoneko_gen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-06-
|
12
|
+
date: 2012-06-04 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bimyou_segmenter
|
16
|
-
requirement: &
|
16
|
+
requirement: &21850440 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 1.2.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *21850440
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: json
|
27
|
-
requirement: &
|
27
|
+
requirement: &21849840 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *21849840
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: test-unit
|
38
|
-
requirement: &
|
38
|
+
requirement: &21849200 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *21849200
|
47
47
|
description: Japanese Text Classifier Generator
|
48
48
|
email:
|
49
49
|
- nagadomi@nurs.or.jp
|