Python3でscikit-learnの決定木を日本語フォントで画像出力する方法のまとめ

概要

Pythonにはscikit-learnという便利な機械学習モジュールがありますが、これを使って決定木学習を実施した際、当然実行したい決定木モデルの画像出力がとても難しく、サンプルコード通りにやってもほとんどの場合でエラーが連発してしまい、決定木が描けないという問題に直面します。日本語を出力しようとするとさらに大変です。
ここを克服しないとなかなか本格的にPythonを使った決定木分析ができないため、なんとか動くところまでもっていき、対策を整理しました。参考までに環境はWindows10、64bit、Python3、scikit-learn0.17、graphviz2.38です。

手順・エラー・対策

手順1:まずサンプルコードを動かしてみる

ソースコード

scikit-learnの決定木の説明ページを見ると、決定木の画像出力のサンプルコードが書いてあり、簡単に言うと以下のようなコードで出力できると書かれています。

#必要なモジュールのインポート
from sklearn.datasets import load_iris
from sklearn import tree
from sklearn.externals.six import StringIO
import pydot

if __name__ == "__main__":
    
    #irisデータの読み込み
    iris = load_iris()
    
    #決定木学習
    clf = tree.DecisionTreeClassifier()
    clf.fit(iris.data, iris.target)
    
    #決定木モデルの書き出し
    dot_data = StringIO()
    tree.export_graphviz(clf, out_file=dot_data,  
                         feature_names=iris.feature_names,  
                         class_names=iris.target_names,  
                         filled=True, rounded=True,  
                         special_characters=True) 
    graph = pydot.graph_from_dot_data(dot_data.getvalue())
    graph.write_png("iris.png") 

エラー

実行すると以下のようなエラーコードが出ました。

AttributeError: module 'pydot' has no attribute 'graph_from_dot_data'

「pydotにはgraph_from_dot_dataなんてないよ」と言われています。調べると、python3の場合、pydotplusを導入しなければならないと書いてあります。python2を使っている場合は不要なのかもしれませんが、早速のジャブです。

対策

Anaconda Prompt等で「pip install pydotplus」と叩き、pydotplusをインストールしましょう。

手順2:pydotplus導入後、再び動かしてみる

ソースコード

手順1でpydotとしていた部分をpydotplusに変更しましょう。

from sklearn.datasets import load_iris
from sklearn import tree
from sklearn.externals.six import StringIO
import pydotplus #pydotplusに変更

if __name__ == "__main__":
    
    #irisデータの読み込み
    iris = load_iris()
    
    #決定木学習
    clf = tree.DecisionTreeClassifier()
    clf.fit(iris.data, iris.target)
    
    #決定木モデルの書き出し
    dot_data = StringIO()
    tree.export_graphviz(clf, out_file=dot_data,  
                         feature_names=iris.feature_names,  
                         class_names=iris.target_names,  
                         filled=True, rounded=True,  
                         special_characters=True) 
    graph = pydotplus.graph_from_dot_data(dot_data.getvalue())  #pydotplusに変更
    graph.write_png("iris.png") 

エラー

今度は以下のようなエラーコードが出ました。

InvocationException: "GraphViz's executables not found"

「GraphViz’s executablesが見つからないよぉ」と言われています。これはネットを調べてもなかなか答えが見つからずデバッグモードで動かして自力で調整する羽目になりました。。。

対策

Graphvizをそもそもインストールしていない、という人は以下サイトから入手してください。
windows | Graphviz – Graph Visualization Software
また、パソコン内のいろいろなファイルを探すことになりますので、Everything等の便利なファイル検索ソフトを入手しておくと役立つかもしれません。
Everything – 窓の杜ライブラリ

本題に戻ります。「GraphViz’s executables」が見つからないというエラーですが、これが指しているものはGraphVizの実行ファイル(例えばdot.exe)です。graphviz.pyの中にあるfind_graphviz()の関数が探索部分に相当しているのですが、どういうわけか64bit等の条件だとうまく動かないようです。必要なことは実行ファイルの場所とリストをreturnすることですので、強引ですが、以下のように変えてしまってください。

# The multi-platform version of this 'find_graphviz' function was
# contributed by Peter Cock
def find_graphviz():
    #dot.exe等の実行ファイルがあるパスをハードコーディングする。
    return __find_executables("C:\\Program Files (x86)\\Graphviz2.38\\bin")

手順3:GraphVizの対策を導入後、再び動かしてみる

ソースコード

実行スクリプトのソースコードには変更ありません。

結果

ようやく以下のように日本語フォントを含まない条件での決定木が描けました。
f:id:Rosyuku:20160816173745p:plain

手順4:featureやtarget_namesを日本語に変えて再トライ

ソースコード

feature_namesとtarget_namesを英語名から適当な日本語に変えてみましょう。

from sklearn.datasets import load_iris
from sklearn import tree
from sklearn.externals.six import StringIO
import pydotplus

if __name__ == "__main__":
    
    iris = load_iris()
    
    clf = tree.DecisionTreeClassifier()
    clf.fit(iris.data, iris.target)
    
    #feature_namesとtarget_namesを適用な日本語に変更
    iris.feature_names = ["ま", "つ", "お", "か"]
    iris.target_names = ["し", "じ", "み"]
    
    
    dot_data = StringIO()
    tree.export_graphviz(clf, out_file=dot_data,  
                         feature_names=iris.feature_names,  
                         class_names=iris.target_names,  
                         filled=True, rounded=True,  
                         special_characters=True) 
    graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
    
    graph.write_png("iris.png") 

エラー

実行すると、エラーコードは返ってきませんが以下のように麻雀牌?に文字化けした結果が返ってきました。フォントが日本語に対応していないようです。どこかでフォントを指定しているはずなのですが、これもなかなか分からず苦労しました。
f:id:Rosyuku:20160816173701p:plain

対策

export.pyを開き、「fontname」で検索すると、400行目あたりで2件ヒットし、fontname=helveticaというソースがありました。つまり、ハードコーディングでhelveticaというフォントに指定されているということですので、ここを日本語フォント名(IPAGothic等)に変更しましょう。本当はMSゴシック等の標準フォントで実行したかったのですが、「MS Gothic」のようにフォント名の中にスペースがあるとエラーが出てしまい、今回は断念しました。参考までに、IPAGothicはIPAフォントのダウンロードからダウンロードしました。

        if rounded:
            #犯人その1
            out_file.write(', fontname=IPAGothic')
        out_file.write('] ;\n')

        # Specify graph & edge aesthetics
        if leaves_parallel:
            out_file.write('graph [ranksep=equally, splines=polyline] ;\n')
        if rounded:
            #犯人その2
            out_file.write('edge [fontname=IPAGothic] ;\n')
        if rotate:
            out_file.write('rankdir=LR ;\n')

手順4:日本語フォントを設定後、再び動かしてみる

ソースコード

実行スクリプトのソースコードには変更ありません。

エラー

実行すると前回文字化けした部分が直り、日本語化された決定木が出力されました。が、今度は別の部分が文字化けしています。前回の図と比較すると、一本線の不等号記号が文字化けしています。この記号は欧州で使われる記号であり、日本語フォントに対応していないのです。なんて面倒なんでしょう。
f:id:Rosyuku:20160816190059p:plain

対策

export.pyを開き「≤(≤←検索するときは&を半角にしてください)」で検索、その部分を「≦」で置き換えます。「≤」というのはISOの規格で一本線の不等号記号を示しています。

        # PostScript compatibility for special characters
        if special_characters:
            #最後の犯人
            characters = ['&#35;', '<SUB>', '</SUB>', '≦', '
', '>']
            node_string = '<'
        else:
            characters = ['#', '[', ']', '<=', '\\n', '"']
            node_string = '"'

手順5:日本語フォント非対応記号を修正後、再び動かしてみる

ソースコード

実行スクリプトのソースコードには変更ありません。

結果

いよいよ今日のその時がやってまいります。。。とうとうできましたね!
f:id:Rosyuku:20160816191151p:plain

まとめ

scikit-learnとGraphvizを組み合わせて決定木を出力する方法を整理しました。一部のフォントが使えないなど、まだ解析は十分ではありませんが、とりあえず最低限のことはできるようになったと思います。同様の悩みで困っている方、もしよろしければ参考にしてみてください。

英語で出力するまで

– pydotplusをインストール
– graphviz.pyのfind_graphvizを修正

日本語で出力するまで

– export.pyのhelveticaを日本語フォントに修正
– export.pyの≤を「≦」に修正

【ご参考】もっと汎用的に分かりやすい決定木が描けるモジュールを見つけました。

ETEを使ってPythonで決定木をシャレオツに表示


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA