概要
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の対策を導入後、再び動かしてみる
ソースコード
実行スクリプトのソースコードには変更ありません。
結果
ようやく以下のように日本語フォントを含まない条件での決定木が描けました。
手順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")
エラー
実行すると、エラーコードは返ってきませんが以下のように麻雀牌?に文字化けした結果が返ってきました。フォントが日本語に対応していないようです。どこかでフォントを指定しているはずなのですが、これもなかなか分からず苦労しました。
対策
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:日本語フォントを設定後、再び動かしてみる
ソースコード
実行スクリプトのソースコードには変更ありません。
エラー
実行すると前回文字化けした部分が直り、日本語化された決定木が出力されました。が、今度は別の部分が文字化けしています。前回の図と比較すると、一本線の不等号記号が文字化けしています。この記号は欧州で使われる記号であり、日本語フォントに対応していないのです。なんて面倒なんでしょう。
対策
export.pyを開き「≤(≤←検索するときは&を半角にしてください)」で検索、その部分を「≦」で置き換えます。「≤」というのはISOの規格で一本線の不等号記号を示しています。
# PostScript compatibility for special characters if special_characters: #最後の犯人 characters = ['#', '<SUB>', '</SUB>', '≦', ' ', '>'] node_string = '<' else: characters = ['#', '[', ']', '<=', '\\n', '"'] node_string = '"'
手順5:日本語フォント非対応記号を修正後、再び動かしてみる
ソースコード
実行スクリプトのソースコードには変更ありません。
結果
いよいよ今日のその時がやってまいります。。。とうとうできましたね!
まとめ
scikit-learnとGraphvizを組み合わせて決定木を出力する方法を整理しました。一部のフォントが使えないなど、まだ解析は十分ではありませんが、とりあえず最低限のことはできるようになったと思います。同様の悩みで困っている方、もしよろしければ参考にしてみてください。
英語で出力するまで
– pydotplusをインストール
– graphviz.pyのfind_graphvizを修正
日本語で出力するまで
– export.pyのhelveticaを日本語フォントに修正
– export.pyの≤を「≦」に修正
【ご参考】もっと汎用的に分かりやすい決定木が描けるモジュールを見つけました。