Julia言語のScikitLearn.jlでテキスト分類

Julia言語の最新版v0.5.0が公開されて、メジャーバージョンv1.0に向けて着実に進化しています。さっそくこちらのブログに変更点が日本語でまとめられています:
Julia言語の0.5の変更点 - りんごがでている

ダウンロードページのディスクイメージでインストールした場合、ターミナルから起動するときは次のようにします:

$ /Applications/Julia-0.5.app/Contents/Resources/julia/bin/julia
julia> 

Juliaは機械学習など数理モデルの計算に向いている高速な処理系ですが、後発の言語ということもあって、豊富な資産がすでにあるRやPythonの機能を直接呼び出すパッケージも充実しています。
JuliaからRを使う - りんごがでている
Python使いをJuliaに引き込むサンプル集 | mwSoft

Pythonのscikit-learnは統一された使いやすいインターフェースで人気がありますが、Juliaからも同じように使えるScikitLearn.jlが公開されているので、これをインストールしてみます:

julia> Pkg.add("ScikitLearn")

ところが、例えばテキスト分類のような自然言語処理のタスクで必要な疎な行列を扱うことが現時点ではまだできないようです:
ScikitLearn.jl/CONTRIBUTING.md at master · cstjean/ScikitLearn.jl · GitHub

幸い、こちらに疎行列をPythonオブジェクトに変換する方法が書かれていたので実際に試してみることにします:
Convert to sparse · Issue #204 · JuliaPy/PyCall.jl · GitHub

テキスト分類の古典的なデータセットである20NewsgroupsMatlab用に加工したデータセットが公開されているのでこれを利用します:

const urlbase  = "http://qwone.com/~jason/20Newsgroups/"
const filename = "20news-bydate-matlab.tgz"
const datadir  = "20news-bydate/matlab/"

function download_20news()
    isfile(filename) || download(joinpath(urlbase, filename), filename)
    isdir(datadir)   || run(`tar -zxf $filename`)
end

function load_20news_Xy(target=:train)
    file = joinpath(datadir, string(target, ".data"))
    I, J, V = Int[], Int[], Int[]
    for line in readlines(file)
        i, j, v = map(m->parse(Int, m), split(strip(line)))
        push!(I, i)
        push!(J, j)
        push!(V, v)
    end
    X = sparse(I, J, V)
    file = joinpath(datadir, string(target, ".label"))
    y = map(m->parse(Int, m), split(readstring(file)))
    return X, y
end

download_20news()
X_train, y_train = load_20news_Xy()

単語の重みには出現頻度をそのまま割り当てて、ロジスティック回帰モデルで分類します:

using ScikitLearn
@sk_import linear_model: LogisticRegression

これでX_trainをそのまま利用するとPythonの密な行列に変換されて極端に処理が遅くなってしまいますので、あらかじめ疎行列に変換しておいてから学習させます:

using PyCall
PyObject(S::SparseMatrixCSC) =
    pyimport("scipy.sparse")["csc_matrix"]((S.nzval, S.rowval .- 1, S.colptr .- 1),
                                           shape=size(S))
Xpy_train = PyObject(X_train)
clf = fit!(LogisticRegression(), Xpy_train, y_train)
@show score(clf, Xpy_train, y_train)

テストデータで分類精度を求めます:

X_test, y_test = load_20news_Xy(:test)
Xpy_test = PyObject(X_test[:, 1:size(X_train, 2)])
@show score(clf, Xpy_test, y_test)

以上の内容を20news.jlファイルにまとめて実行すると:

julia> include("20news.jl")
score(clf,Xpy_train,y_train) = 0.9988463927588961
score(clf,Xpy_test,y_test) = 0.7520319786808795
0.7520319786808795

julia>