はじめに
こんにちは、事業会社で働いているデータサイエンティストです。
この記事は、新しい統計学・機械学習のモデルの提案や、実践的なR言語のテクニックではなく、 どちらかというとR言語のコミュニティが今後R言語をどうしていくべきかに対して、 R言語歴7年目の一個人として提案し、関心のある皆さんにも考えていただきたい内容になります。
具体的には、私の経験を生かして、R言語を用いたアルゴリズムのビジネス本番環境への実装する際に直面する本当の困難について色々紹介したいと思います。
R言語は実装に向いていないという言説は皆さんも普段よく聞くと思いますが、R言語は本当に実装に向いていないのか、その理由は妥当か、を私の経験と私が聞いた話をベースに皆さんと共有したいと思います。
R言語は実装に向いているかを考える必要はあるか?
ここでは、ビジネスやアカデミアでR言語を目標実現のためのツールとして使う「現場ユーザー」とR言語の将来について考える「R言語コミュニティ」に分けて説明します。
現場ユーザー
「R言語は統計ツールだから実装に向いていないのは当たり前だわ、考える必要はない!Pythonを使えばいいじゃん!」と思う現場ユーザーの方もいるかもしれません。もしリーダーの技術選定でしたらメンバーとしてそれに従いましょう。チームには規律が必要です。
ここでは、リーダーの方にお伺いします。本当に「考える必要はない!」ですか?実は、深く考えないことによって、あなたは本当の課題の所在を特定し、ピンポイントで効果的に改善する機会を失います。これによって、せっかくPythonに切り替えても、Pythonの力を十分に発揮することができず、結局R言語のままでも起こりうる困難をPythonで経験するだけになるかもしれません。
このように、本質的な問題を改善しないと、プロジェクトが失敗する原因を特定できず、同じ過ちを繰り返すリスクがあります。なので、ぜひ本記事の内容をご確認いただき、R言語に問題があるのか?もしあるのであれば、果たしてどんな問題なのか?を考えるきっかけにしていただければ幸いです。
R言語コミュニティ
R言語のコミュニティの一員としては、今後のR言語の進化を考え、他のコミュニティメンバーと議論する必要があります。
その際に、まずAS IS(現状の課題)を把握し、明確に言語化する必要があります。
もちろん、AS ISの課題が全てTO BE(理想の将来像)において解決しないといけないというわけではないです。課題を解決するかどうかを決めるのは、課題解決がもたらすリターンと課題解決のために払わないといけないコスト(開発工数、R言語の大規模な変化、など)を天秤にかけて計算する必要があります。
その中で、「なんかさ、R言語って、本番環境への実装に向いてないね!」のようなふわっとした感覚から、TO BEのリターンとコストを言語化するのは難しいです。
R言語のコミュニティの皆さんも、ぜひ本記事を通じて、ビジネスの側面から見たR言語をより深く考えていただければと思います。
スコープ
さて、本題に入る前に、R言語はGo言語やPythonのように、普通のプログラミング言語なので、用途が多岐に渡ります。範囲を絞らないと、この記事は本になってしまいます。
なので、本記事の内容を、ビジネス側に特有の、R言語で本番環境で動く、常に計算タスクをこなさなければならないAPIを作成する場合に絞り、これを「実装」と定義します。
したがって、下記のR言語用途は今回の記事では紹介しません:
-
単発的なレポート作成:論文執筆など
-
バッチ処理:毎日深夜2時に動き出して、当日朝7時ユーザーに配信するメルマガにどんな内容を載せるかを決めるシステム
理由としては、単発的なレポート作成とバッチ処理は、APIほど高いパフォーマンスを求められないケースが多く、私がこれから紹介するR言語の本当の限界にぶつかることがあまりないからです。
結論:R言語は本番環境への実装に向いていない
結論から言いますと、R言語はシングルスレッドという制約のため、実装に向いていません。シングルスレッドとマルチスレッドとは何かは追って記事内で説明します。
一方で、R言語の実装に否定的な論点はシングルスレッドではなく、主に下記の三点になります:
-
R言語は属人化する
-
R言語は遅い
-
R言語にシステム構築用のフレームワークがない
ですが、実はどれもR言語自体とあまり関係ないことが経験からわかりましたので、詳細を説明いたします。
論点1:R言語は属人化する
まず、R言語は属人化するというのは、なかなか不思議でふわっとしている論点です。属人化につながる要因は
-
エンジニアの人手不足によるベンダー依存
-
ドキュメントを残す習慣が浸透していない
などがメインで、言語自体が属人化の原因にあるのは論理上あり得ないです。
なので、ここでは「R言語は属人化する」という論点の行間を読む必要があります。おそらく「R言語は属人化する」の真の意味は、「R言語の知見があるエンジニアが少ない」ということなのではないでしょうか?
「R言語は属人化する」という指摘は、「R言語の知見を持つエンジニアが少ない」という問題を指していると考えられます。つまり、「統計学や機械学習を実装する言語にエンジニアが精通しているべきだ」という前提が背景にあるのではないでしょうか。
さて、「統計学や機械学習を実装する言語にエンジニアが精通しているべきだ」というのは本当でしょうか?もちろん、知っておくのはいいことです。ここでは必要性について少し深掘りしていきたいです。
まず、統計学・機械学習を駆使するための知識は、エンジニアの知識とほぼ関係ないという事実に向き合わないといけません。考えてみてください。
-
事前分布の設計
-
操作変数の選定
-
DIDにおける平行トレンドの仮定の検証
-
離散選択モデルが仮定する効用関数の形の妥当性の検証
-
処置後変数の特定
の中で、データ指向アプリケーションやロードバランシング、サーバーサイドレンダリングなどの知識で回答できるのはどれでしょうか?
もちろん、エンジニアがデータサイエンスに関心を持つのはいいことで、技術の交流は生産性を上げ、イノベーションを起こすために必要です。ただ、短期的には、R言語をやめてPythonなどのエンジニアが得意とする言語に切り替えただけで、エンジニアが貢献できる範囲が一気に広がることは現実的には難しいと思われます。
数年前、エンジニアを巻き込もうと、R言語からPythonに切り替えた某組織の某プロジェクトで何が起きたのかというと、インフラエンジニアが「ロジスティック回帰は回帰モデルだから分類できない」と言い出してチームが大混乱した話を、あるオンラインイベントで聞きました。
もちろん、エンジニアの価値を否定するわけではなく、逆に、セキュリティについて何もわからないデータサイエンティストがセキュリティについて「クリエイティブ」な意見を言い出したら、チームは同じように混乱するでしょう。
なので、ここで重要なのは、お互い得意なところをやって、得意でないところをカバーし合うための責任分解点の設定です。
具体的には、モデル構築時はデータサイエンティストを中心に実施し、モデル構築が完了した際に、エンジニアチームとモデルをシステムに組み込む方法についてディスカッションして、お互いやるべきことを擦り合わせるべきです。
統計学や機械学習モデルには、私が勝手に本体機能と定義するコアな部分があり、概ねコード量としては少なく、一人か二人のデータサイエンティストが必要に応じて少ない工数で対応できる量です。本体機能はデータサイエンティストが慣れている言語にして、他のコードをエンジニアが得意な言語にして、お互いの連携方法を決めておけばいいです。
本体機能とはなんなのか。毎日深夜2時に動き出して、当日朝7時ユーザーに配信するメルマガにどんな内容を載せるかを決めるシステムについて考えましょう。
このバッチがどんなことをすると、おそらく
-
SQLを生成し、配信対象ユーザーと対象アイテムを取得する
-
対象ユーザーと対象アイテムの全ての組み合わせについてマッチ度スコアを計算する
-
マッチ度スコアをスキーマ定義に合わせてテーブルに整形する
-
テーブルをDBに送信し、投入する
なのかなと思います。その中で、統計学・機械学習モデルと関係するのは、「対象ユーザーと対象アイテムの全ての組み合わせについてマッチ度スコアを計算する」だけです。さらにいうと、その中の「マッチ度スコアを計算する」手順です。「マッチ度スコアを計算する手順」のことを本体機能と言います。
極端な話、
predict(model, new_data)
で終わります。この一行の関数は一人で問題なく保守運用できます。
話が長くなってしまいましたが、結論として、データ分析はデータサイエンティストが担当した方がいいタスクのため、データサイエンティストファーストで利用言語を選びましょうということになります。なので、データサイエンティストとプロジェクトの要件にとってR言語が最適であれば、R言語を使うべきです。
R言語を扱えるエンジニアが少ないことを理由にRを避けるのは、このセクションで述べたように正当化は難しいでしょう。
論点2:R言語は遅い
R言語が遅いという指摘に関しては、まずは無駄な処理をしていないかを精査する必要があります。
実際の話で説明します。
最近、とある単発のデータ分析依頼で政治学方法論で開発された構造トピックモデルを利用しました。その際に、新しいドキュメントのトピックに分布を推定するstm::fitNewDocuments()を利用しました。stm::fitNewDocuments()は元々大量のドキュメントのトピック分布を同時に推定することができるが、とある事情で、
documents |>
purrr::map(
\(this_document){
stm::fitNewDocuments(my_stm, this_document)
}
)
のように逐次に処理しないといけませんでした。その際に、逐次処理が一気で処理するやり方と比べて、想像できないくらい遅いことが判明しました。stmのソースコードを確認したら、
sigobj <- try(chol.default(sigma), silent=TRUE)
if (inherits(sigobj,"try-error")) {
sigmaentropy <- (.5*determinant(sigma, logarithm=TRUE)$modulus[1])
siginv <- solve(sigma)
} else {
sigmaentropy <- sum(log(diag(sigobj)))
siginv <- chol2inv(sigobj)
}
のような、一回やって結果を保存すればいい重い計算を毎回回していることが判明しました。これを解消するために、stm::fitNewDocuments()の処理を精査して、あらかじめ計算できる変数を洗い出して事前に計算して、逐次処理の負担を約90分の1にしました。
詳細は出せませんが、ベンチマークをお見せします。まずは性能改善前の処理時間です:
Time difference of 4.766318 secs
次は性能改善後の処理時間です:
Time difference of 0.05089617 secs
上記のようなstm::fitNewDocuments()の内部計算がいわゆる無駄な処理です。
どうしてR言語にこのような無駄な処理が多いのかというと、有用なパッケージを作成したのは基本的には学者で、研究目的で全てを設計してしまったという背景があるのではないかと思います。なので、ビジネス側が使おうとする時は、少し処理内容を微調整したり、関数を再設計したりする必要があります。この処理を書き換えるプロセスはビジネス側でしか経験できない貴重な経験であり、視座を高めてくれるので、ぜひ恐れずに積極的にやりましょう。
次に、R言語内で改善してもダメだったら、Rcppを活用して思い処理をC++に変えてみてください。R言語で実現したいコードを書いて、ChatGPTなどの生成AIに「Rcppで書き換えてください」とお願いすればいいです。ただ、Rcppのコード実例が多くないからか、生成AIが一発で正しいコードを書いてくれることはあまりないです。ただ、基本的には生成AIと色々試行錯誤すれば、質のいいRcppコードに辿り着きますし、あなた自身にとってもいい学習機会なので、ぜひ恐れずにチャレンジしてください。
まとめると、R言語は遅いから使うべきではないという主張も、実はプログラム自体の設計が悪い側面の方が強く、R言語自体とはあまり関係ないです。プログラム設計の問題から逃げてPythonに変えただけだと、問題は消えてくれません。
論点3:R言語にシステム構築用のフレームワークがない
フレームワークはあります。
フロントエンドの作成にはshinyという便利なパッケージがあります。具体的な利用方法については下記の記事をぜひご参照ください:
また、shinyで困ったら、とりあえずChatGPTなどの生成AIに聞きましょう。ただ、重要なのは、論点2のところでも述べましたが、生成AIが一発で正しいコードを書いてくれることはあまりないです。なので、どちらかというと、生成AIをパートナーとして使いこなして、フロントエンドの知識とshinyというフレームワークを効率的に身につけてください。
APIに関しても、plumberというパッケージが用意されています。
詳細はぜひこちらの記事を参照してください:
こちらも同じように、生成AIを学習パートナーとして駆使するといいと思います。
真犯人:シングルスレッド
さて、「R言語は属人化する」、「R言語は遅い」、「R言語にシステム構築用のフレームワークがない」というよくR言語を否定するために用いられる論点は、実はどれもR言語自体の問題ではなく、実装方法やコミュニケーション、チーム編成など利用者側の問題ということがわかりました。
では、これでR言語は本番環境への実装に向いていると主張できますか?残念ながら、違います。R言語は「R言語は属人化する」、「R言語は遅い」、「R言語にシステム構築用のフレームワークがない」という表層的な指摘ではなく、もっと深い理由で、本番環境への実装に向いていません。真犯人は、R言語はシングルスレッドの言語であるという事実です。
シングルスレッドが生み出すパフォーマンスの問題は、主にエンジニアの方に聞いたり、Googleで確認したり、ChatGPTに聞いたりして手に入れました。政治学方法論と国際関係論が専門で、シングルスレッドの課題について誤った記述がございましたらぜひご指摘いただければ幸いです。
R言語のユーザーにとって下記の二本の記事は特に有用なのでぜひ確認してください:
シングルスレッドの言語の限界としてわかりやすいものは下記の二つです:
-
外部のサポートなしでは、複数のCPUコア上で計算を独立してスケジュールすることはできない
-
すべてのタスクが計算負荷の高い処理であり、シングルスレッド実行モデルに依存している場合、そのプログラムは一度に1つのCPUコアしか使用できない
詳細を話すと内容が長くなってしまいますので、シングルスレッドの限界としてはまた別の記事で紹介したいですが、要するに、計算リソースをフル活用できないため、本番環境で発生するような大量の計算リスエストを捌ききれないということです。
具体的には、ECサイトのレコメンドエンジンを想像してください。R言語を動かすバーチャルマシンには4つのCPUコアがあります。100人のユーザーが同時に商品推薦のボタンを押した場合、R言語は最大で4人のユーザーにしか同時に商品を推薦できません。つまり、残りの96人のユーザーには待機が発生し、その待ち時間が次第に雪だるま式に増加することになります。計算が軽い場合でも、処理は最大で4人分ずつしか行えないため、待機時間は長引きます。その結果、計算が終わらないのに、CPUの利用率が低い現象がログとして観測されます。
これはfurrr
などの並列計算パッケージでは解決できません。なぜなら、これらのパッケージはタスクを複数のコアに分割して並列実行する仕組みを提供しますが、最終的にはR言語は利用可能なCPUコア数までしか同時に計算を実行できないからです。100人のユーザーに同時に商品を推薦したい場合、100個のCPUコアがついている強力(かつ高価)なバーチャルマシンを用意する必要がありますが、これはコスト面で非常に現実的ではありません。
この問題を本質的に解決するには、Go言語やPythonのようなマルチスレッドな言語を活用して、リクエストを効率的に処理する方法を検討する必要があります。これらの言語では、複数のスレッドを利用して同時に複数のリクエストを処理できるため、CPUのコア数に依存せず、R言語のような単一スレッドで動作する環境よりも遥かに高い同時処理能力を実現することが可能です。
これがR言語の本当の壁です。R言語は本番環境への実装に向いていません。
これは2000人のR言語に精通したエンジニアを雇っても(論点1:R言語は属人化する)、R言語の一部の処理を丁寧に書き換えて早くしても(R言語は遅い)、R言語向けのシステム構築のフレームワークを活用しても(R言語にシステム構築用のフレームワークがない)、解決できない、根本的な問題です。
R言語自体を進化させない限り、現状の中でR言語のみで努力しても解決は難しいです。
ここで少し「R言語は実装に向いているかを考える必要はあるか?」のセクションで述べた内容に立ち戻りますが、R言語単体で本番環境への実装に向いていない本当の理由を考えないリスクを改めて整理します。
安易に「Pythonは属人化しない!」などといった実は正しくない理由でPythonに切り替えても、シングルスレッド性による制約は実は勝手に消えてくれません。処理をマルチスレッドにして、Pythonというマルチスレッド言語の性能を最大限まで引き出すためには、処理をスレッドセーフな形にする必要があります。
具体的には、各スレッド間で共有されるデータやリソースへのアクセスを適切に制御し、安全な設計を心がけることが重要です。加えて、既存のコードベースがシングルスレッド前提で設計されている場合、コードのリファクタリングが必要となる場合もあります。スレッドセーフの知識がない状態で処理をR言語からPythonに書き換えるだけだと、むしろパフォーマンスの悪化やデバッグ困難なバグの発生につながりかねません。
本記事が取り上げたR言語の問題だけでなく、物事の本当の背景と原因を深く考えないと、セカンドベストな解決策、ないしは改悪策の提案に繋がります。なので、ぜひ普段から物事の本質について一歩踏み込んで深く考えてみてください。
では、どうすればいいの?
では、どうすればいいのかというと、現場のユーザーとR言語コミュニティに分けて回答します。
現場ユーザー
現場のユーザーは、まずPythonなどのマルチスレッドな言語に同様の機能があるかを確認し、もしあれば切り替えましょう。
ただ、どうしてもR言語にしかない機能や、R言語の方が実現しやすいようであれば、Go言語やPythonのようなマルチスレッドな言語にR言語の処理を呼び出すアーキテクチャを考えてみてください。
R言語の問題は利用可能なCPUコア数以上の計算を同時に実行する能力の欠如なので、CPUコア数以上のR言語のセッションを呼び出して、必要なデータを渡す機能を代行してくれる他の言語・ツールと協働すればいいです。
R言語コミュニティ
R言語コミュニティは、より高い視座でこの問題と向き合わないといけません。
まず考えないといけないのは、そもそもR言語にマルチスレッド性を持たせるべきかということです。「課題」と「解決しなければならない課題」は違います。
現状は上述したように、R言語をGo言語やPythonで呼び出す設計にしておけば、マルチスレッドに計算タスクを捌いて価値をデリバリーすることができます。このシステム構成から、Go言語とPythonの出番をなくす意味は果たしてあるのかをしっかり考えないといけません。
確かに、Go言語とPythonをシステム構成からなくすことで、データサイエンティストが単独でシステムを構築することがより簡単になります。
ただ、論点1のところで述べたように、言語は知識を含意しません。データサイエンティストが得意とするR言語をよりシステム構築向けに進化させても、結局エンジニアリングの高度な知識がないと、商用に耐えられるようなシステムを作るのは難しいでしょう。
考えてみてください。
-
データ指向アプリケーション
-
ロードバランシング
-
サーバーサイドレンダリング
-
スレッドセーフ
の中で、事前分布の設計や、操作変数の選定、DIDにおける平行トレンドの仮定の検証などの知識で回答できるのはどれでしょうか?
次に、R言語をマルチスレッドにする際に、R言語はおそらく何らかの便利な機能を失うか、それを失わないための膨大な努力が必要です。具体的には、初心者が普段意識しないガーベージコレクションの機能がおそらく変わります。なぜならR言語をマルチスレッドにする際には、ガーベージコレクションの動作がスレッド間で整合性を保つ必要があるためです。
現在のR言語はシングルスレッド設計を前提としており、メモリ管理やガーベージコレクションがこの単純なモデルに最適化されています。しかし、マルチスレッド化すると、各スレッドが同時にメモリを操作する可能性が生じるため、スレッドセーフなガーベージコレクションの実装が求められます。この変更により、ガーベージコレクションのパフォーマンスが低下したり、複雑なロック機構の導入が必要になったりする可能性があります。これらの課題を解決しない限り、現在のR言語のシンプルさや効率性が損なわれるリスクがあり、その回避には膨大な開発コストと技術的な工夫が必要です。
開発コストとR言語が使いづらくなるリスクは、マルチスレッド化がもたらすメリットを果たして超えるのか。R言語コミュニティはこれを精査して投資判断すべきです。
結論
いかがでしたか?
R言語が「実装に向いていない」という主張には多くの背景と誤解があることを見てきました。その中で、
-
属人化するという批判は、R言語そのものではなく、必要に応じた責任分解で解決可能であること
-
R言語が遅いという批判には、パフォーマンスチューニングの視点や利用方法の工夫が求められること
-
R言語にフレームワークがないという批判には、R言語には実はきちんと用意されていること
を説明しました。
また、R言語単体でシステム構築に向いていないが、Go言語やPythonとの組み合わせで本当の課題であるシングルスレッド性がもたらす制約を超えることができます。
これらを踏まえると、上記の理由でR言語を本番環境に実装するのはできないと断定するのは適切ではありません。むしろ、データサイエンスの本体機能に集中する部分では、R言語はシンプルで効率的に役割を果たすことができ、プロジェクト全体の成功に貢献します。
重要なのは、言語そのものではなく、適切な責任分解点を設定し、データサイエンティストとエンジニアが連携する環境を構築することです。このアプローチにより、R言語の強みを最大限に生かしつつ、チーム全体の効率と生産性を向上させることが可能になります。
R言語は、その特性を正しく理解し、適切に活用することで、十分に実装で役立つツールであることを再確認し、選択肢として考えるべきだと思います。
ぜひビジネスとアカデミアの場でR言語をどんどん活用してください!
私たちと一緒に働きたい方は下記のリンクを参照していただければと思います:
ご応募をお待ちしております。