Clojureとは
Clojureは様々な言語と横断的につながるフレームワークを持っている。たとえばClojureScript(cljs)は、google closureを利用してcljsコードをベアなJavascriptにコンパイルするしくみを持つ(typescript等の現在のAltJavascriptと同様のしくみ)。また同様にCSSも変換する仕組みを持つことから、Clojureだけでフロントエンドを記述することができる。
この仕組みとClojureのベースプラットフォームであるJava((既存のシステムの50〜60%がJavaで構築されている)が持つ様々なライブラリーがClojureでもネイティブに利用可能であること、さらにClojure自身も後述するマイクロサービスに用いられるような優れたフレームワーク(datomic、pedestal、duct等)も持っていることなどから、特にwebシステム等の構築の分野ではWorld Wide(日本での事例は先進的な一部の企業に限られる)で広く用いられている。
ClojureとPythonやRを連携させた機械学習
それに対して機械学習の領域では、PythonやRのような豊富なライブラリーを持つ環境が利用されててほぼデファクトとなっているが、これに対して初期(2007年〜2017年頃)のClojureでは、CやPython、R等と繋げるしくみも開発されていたが、相手先のライブラリを自由に扱えるレベルではなく、最新のアルゴリズムを駆使することにハードルがあった。
これに対して近年(2018年〜)、libPython-cljのようなPython環境と相互に運用可能なフレームワークが現れたり、またJavaやCのライブラリを活用した数学的フレームワークfastmathや、深層学習のフレームワークCortex、Deep Diamond等が開発されたりすることで、機械学習へのアプローチが積極的に検討され、Clojureの機械学習のコミュニティとして有名なscicloj.ml等で活発に議論されるようになった。
さらに次世代のAI技術として注目されている第四世代のAI技術は、第三世代の技術である深層学習技術と第一、第二世代の技術である知識・記号推論技術を融合することが提案されており、第一、第二世代の知識・記号推論技術に用いられたLispを祖先に持つClojureが、Pyhton等と融合した機械学習技術を持つことで、単一のプラットフォーム上で第四世代のAI技術を構築できる可能性が期待され、欧米を中心に盛んに検討が行われている。
libpython-cljを使った実装とPythonライブラリのClojureでの活用
今回はこれらのうち、libpython-cljの立ち上げと利用について述べる。
libpython-cljはPython を Clojure に深いレベルで統合することを目指して開発されたもので。これは、まるで Clojure 名前空間であるかのように、Python モジュールをロード/使用できるようにしたいということを意味し、また、Clojure を使用して Python オブジェクトを拡張できるようにすることを目指している。彼らの詳細なビジョンは Clojure Conj 2019 での講演で知ることができる。
詳細の情報はgitのページに掲載されている。exampleのページにはGPT2 text generation from hugging-face, MXNet MNIST classification using the Module API, Pytorch MNIST, Matlib PyPlot, NLTK, SpaCy, Sci SpaCy, Seaborn, UMAP, TRIMAP, Igraph, Leiden, Sklearn, Facebook Prophet, Pygal, Bokeh, OpenCV, psutil, diffprivlb, transformers等の主だったpythonライブラリを使った例について述べられている。
具体的な実装について述べる。まずClojureの開発環境立ち上げに関しては以下のページを参照のこと、libpython-cljをM1 macに導入するためには、JDK17を用いる必要がある。現在デフォルトではJDK18が入っているが、いくつかエラーが生じているようであり、libpython-cljの作成者も次の19に対応するからということで、ウェブ上でのerrorQAを見ると、JDK18には対応する意思はないようである。JDK17のインストールは例えばbrewを使う場合は”brew install openjdk@17″でインストールしてパスを通すか、jenvを使ってインストールし、切り替えられるようにする。
次に新規のプロジェクトをleiningenで作成する。
lein new linpython-test01
プロジェクトファイルは以下のようにする。
;; project.clj
(defproject libpython-clj-test01 "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
:url "https://www.eclipse.org/legal/epl-2.0/"}
:dependencies [[org.clojure/clojure "1.11.1"]
[clj-python/libpython-clj "2.018"]
]
:repl-options {:init-ns libpython-clj-test01.core}
:jvm-opts ["--add-modules" "jdk.incubator.foreign"
"--enable-native-access=ALL-UNNAMED"])
libpython-cljライブラリは最新バージョンは”2.018″となる。また、jvmオプションとして”:jvm-opts [“–add-modules” “jdk.incubator.foreign””–enable-native-access=ALL-UNNAMED”]”を加える必要があることに注意が必要となる。
ClojureのREPL開発は「SublimeText4でのClojureの開発環境」等を参照のこと。
まず名前空間の設定とconfigとしては以下のようになる。
(ns libpython-clj-test01.core
(:require
[libpython-clj2.python :as py :refer [py. py.. py.-]]
[libpython-clj2.require :refer [require-python]]))
(py/initialize! :python-executable "/Users/XXX/python3"
:library-path "/Users/XXX/lib/python3.9")
“py/initialize!…”でpython環境の設定を行う、まず”:python-executable “/Users/xxx/python3″では”which python3″で表示される。動作させるpythonのパスをコピーする。次にライブラリパスである”:library-path “/Users/xxx/lib/python3.9″に関しては、一旦pythonを動作させた後、”import sys””print(sys.path)”でライブラリのパスを確認してコピーする。
まずはHello Worldから、
(py/run-simple-string "print ('Hello World!')") ;;Hello World!
REPLをかけると、pythonのコードである”print(‘Hello Wolrd!’)”が実行されて、”Hello World!”が出力される。
次にnumpyのインポートと実行を行う。
(require-python '[numpy :as np]) ;ok
(py/from-import numpy average) ;;#'libpython-clj-test01.core/average
(average [1, 8, 4, 10]) ;;5.75
さらにもう少し高度なライブラリとして、自然言語理解と自然言語生成の汎用アーキテクチャ(BERT、GPT-2など)と何千もの事前学習すみモデルを提供するライブラリである”Transformerモデルの概要とアルゴリズム及び実装例について“でも述べているtransformersを利用してzero-shot-classificationを行なった場合
(require-python '[transformers :bind-ns])
(def classifier (py. transformers "pipeline" "zero-shot-classification"))
(def text "French Toast with egg and bacon in the center with with maple syrup on top.
Sprinkle with powdered sugar if desired.")
(def labels ["breakfast" "lunch" "dinner"])
(classifier text labels) ;;{'sequence': 'French Toast with egg and bacon in the center with with maple syrup on top. Sprinkle with powdered sugar if desired.',
;; 'labels': ['breakfast', 'lunch', 'dinner'],
;;'scores': [0.9893278479576111, 0.00738490978255868, 0.0032872725278139114]}
分類したいテキスト”French Toast with egg and bacon in the center with with maple syrup on top. Sprinkle with powdered sugar if desired.”とカテゴリ[“breakfast” “lunch” “dinner”]を入力して分類すると、元々持っているfacebook/bart-large-mnliモデルを使って計算されて、Breakfastと判定される。
次に説明できる機械学習のツールとして紹介した「lime」の適用(前述のtransformerのデータを説明する)は以下のようになる。
(require-python '[lime.lime_text :as lime])
(require-python 'numpy)
(def explainer (lime/LimeTextExplainer :class_names labels))
(defn predict-probs
[text]
(let [result (classifier text labels)
result-scores (get result "scores")
result-labels (get result "labels")
result-map (zipmap result-labels result-scores)]
(mapv (fn [cn]
(get result-map cn))
labels)))
(defn predict-texts
[texts]
(println "lime texts are " texts)
(numpy/array (mapv predict-probs texts)))
(predict-texts [text])
(def exp-result
(py. explainer "explain_instance" text predict-texts
:num_features 6
:num_samples 100))
(py. exp-result "save_to_file" "explanation.html")
結果として、以下のようなものが得られる。
transformaersで分類したいテキスト”French Toast with egg and bacon in the center with with maple syrup on top. Sprinkle with powdered sugar if desired.”とカテゴリ[“breakfast” “lunch” “dinner”]を入力して分類すると”breakfast”の判定結果が出たが、limeでそれらを判定させた場合、単語の中で最も影響が大きかったものが”Toast”であるという結果が出ている。
コメント
[…] ClojureとPythonの連携と機械学習 […]
[…] ClojureとPythonの連携と機械学習 […]
[…] Clojure : ClojureはJVM上で動くLISPである為、前述のJavaのライブラリはそのままネィティブに動作させることができ、またJavaのJNIインターフェースを介してCのライブラリを直接利用することもできる。更にマクロ機能等の言語の柔軟性を用いて”ClojureとPythonの連携と機械学習“や、”ClojureとRの連携による統計的学習“などに述べられているようにPythonやRのライブラリも利用することができる。Clojureネイティブの線形代数ライブラリとしては行列を扱う”core.matrix“、”clatrix“、一般的な線形台数を扱う”incanter“などがある。 […]