FFRIエンジニアブログ

株式会社FFRIセキュリティのエンジニアが執筆する技術者向けブログです

インターンシップ報告 4

ご挨拶

この度FFRIの基礎技術研究部で 2 週間のインターンシップに取り組ませていただきました、樋川と申します。本記事ではインターンシップで取り組んだ内容や感想などについて報告いたします。

応募動機

私が本インターンシップに応募した動機は、MWS Dataset の一部として公開されている FFRI Dataset に興味があり、それを用いて実際に機械学習を行うことができる本インターンシップの内容に惹かれたからです。

インターンシップの概要

本インターンシップでは大きく分けて 2 つのテーマについて取り組みました。

1 つ目は「分散処理基盤を用いた機械学習」です。AWS の EMR 上で機械学習のための分散処理基盤を構築し、100GiB を超える大容量・非構造化データセットを活用した機械学習が可能かどうかについて検証いたしました。

2 つ目は「Strings の活用と精度の比較」です。1 つ目のテーマにて分散処理基盤を用いた機械学習が行えることを確認できたため、次のステップとして特徴量を工夫することによる検知精度の向上を目指しました。本インターンシップでは様々な特徴量の中でも特に Strings に着目して検証いたしました。

分散処理基盤を用いた機械学習

背景

本インターンシップで用いる FFRI Dataset 2019 は総データ量が 100GiB を超える大容量データセットであるため、マシンスペックの問題から単一のマシン上で Python の sklearn などを用いた機械学習が困難です。 そこで、本インターンシップでは Amazon が提供する Hadoop クラスタの構築・運用サービスである EMR を用いて分散処理基盤を構築し、pyspark の ml を用いて機械学習を行いました。

基盤構築

今回の用途に適したインスタンスの種類や予算などを考慮し、EMR で提供されるメモリ最適化インスタンスの 1 つである r5.xlarge(vCPU:4, メモリ:32GiB)を 9 台(マスターノード:1 台、コアノード:8 台)用いて分散処理基盤を構築することにしました。マスターノードとは分散処理基盤全体の管理や制御を担うノードのことで、コアノードとは分散処理用の計算資源(インスタンス)として割り当てられ並列計算を行うノードのことです。 このとき、構築した基盤の資源を最大限活用するために「# Amazon EMR で Apache Spark アプリケーションのメモリをうまく管理するためのベストプラクティス」を参考に以下のように設定しました。

spark.executor.cores = 3
spark.executors.memory = 27684M
spark.yarn.executor.memoryOverhead = 3072M
spark.driver.memory = 27684M
spark.driver.cores= 3072M
spark.executor.instances = 7
spark.default.parallelism = 42

しかし上記の設定では直接的なエラーは出ずに構築に成功するものの、Spark で接続できない状況となりました。原因の追求のため基盤の稼働状況をモニタリングしたところ、本来コアノードのみにインスタンスが割り当てられるべきであるのに対してマスターノードにインスタンスが割り当てられており、コアノードが 1 台遊んでしまっている状態を確認できました。このことから、インスタンスが割り当てられたことでマスターノードが CPU コアもしくはメモリ不足になり、Spark の接続のための処理が正常に行われていないのではと考えました。本インターンシップでは、マスターノードにインスタンスを割り当てない設定の方法を確認できなかったため、マスターノードにインスタンスが割り当てることを許容してコアノードが遊ばないように spark.executor.instances を 8 に増やし、各インスタンスに必要な CPU コアもしくはメモリの設定を少なめにすることで正常に動作させることができないかを検証しました。検証の結果、各インスタンスに必要なメモリ量を少なめに設定することで正常に構築及び接続ができました。その際の設定を以下に示します。

spark.executor.cores = 3
spark.executors.memory = 18432M
spark.yarn.executor.memoryOverhead = 2048M
spark.driver.memory = 18432M
spark.driver.cores= 2048M
spark.executor.instances = 8
spark.default.parallelism = 48

サンプルモデルでの学習

構築した分散処理基盤上で機械学習を行えるかを検証するために pyspark.ml.classification の RandomForestClassifier をデフォルトパラメータで定義し、FFRI Dataset 2019 において特徴量として定義しにくい Vector 型や String 型のデータを除いた特徴量(次元数 138、sparse)を入力として学習しました。また、訓練データとテストデータの比率は 8:2 としました。

結果として 17 分程度で訓練が、8 分程度でテストが完了し、Accuracy が 0.881 であることが確認できました。

以上より、学習に少なからず時間がかかるものの、分散処理基盤を活用して大容量・非構造化なデータセットを用いた機械学習が可能であることが確認できました。

課題

分散処理基盤の構築において Amazon の公式で掲載されているベストプラクティスと同じ手法での設定では正常に動作しなかった点について、本インターンシップでは各インスタンスのメモリの使用量を本来より小さく設定することで解決してしまったため、未割り当てのメモリ領域が多く存在してしまいました。これではせっかくの計算資源を無駄にしてしまっているため、より最適な設定方法を探す必要があると感じました。

Stringsの活用と精度の比較

背景

MalwareDataScienceでも示されているように、マルウェア検知において strings コマンドで取得した文字列の集合(以降 Strings とする)が有効であることは確認されています。そこで、本データセットにおいても同様の結果が得られるか、また Strings の扱い方を工夫することでより良い精度を実現できないかという考えのもと検証を行いました。

StringsのFeature Hashingを活用したマルウェア検知

MalwareDataScienceを参考に、Strings を特徴次元数 20000 で Feature Hashing したものを入力として、pyspark.ml.classification の RandomForestClassifier で学習しました。このとき、Feature Hashing には pyspark.ml.feature の HashingTF を用いました。

学習の結果、特徴となる 20000 次元が sparse なものであったため 30 分程度の時間で学習でき Accuracy は 0.772 でした。しかし、MalwareDataScience やメンターの方が過去に検証した結果から想定すると Accuracy が 0.9 近くは出るであろうと考えられたため、これは想定より大幅に低い結果でありました。このとき、MalwareDataScience やメンターの方は sklearn.feature_extraction の FeatureHasher や sklearn.ensemble の RandomForestClassifier を用いていたことから、この sklearn と pyspark の仕様の違いが結果に影響しているのではないかと考えました。

そこで、20000 次元で Feature Hashing した Strings を sklearn で学習させることは困難と考え、代わりに FFRI Datasetの中から比較的データサイズが小さく sklearn での学習が可能かつマルウェア検知に有効であることが確認されている impfuzzy を抽出し、pyspark と sklearn の学習結果を比較することで仕様の違いを調査しました(このとき、sklearn での学習には Amazon Sagemaker で構築した環境を用いました)。

pysparkとsklearnの仕様の違いに関する検証

pyspark と sklearn の各々において、impfuzzy に対して次元数 100 で Feature Hashing をした後デフォルトパラメータの Random Forest で学習して Acuuracy を確認した結果、表 1 のようになりました。この結果から、やはり仕様の違いが存在することが確認できたため更に詳しく調査しました。

表 1 pysparkとsklearnにおいてimpfuzzyのFeature HashingをデフォルトパラメータのRandom Forestで学習した際のマルウェアの検知精度

まず、Feature Hashing の仕様について調査したところ大きく 2 点の仕様の違いが存在しました。

1 点目はハッシュ化した各要素を指定した次元数(N)のベクトルに格納するときの処理です。sklearn では pyspark での実装の内容にハッシュの衝突回避のための工夫が追加されておりました。この違いによる精度の差は微小であったため、今回は pyspark の仕様に合わせて学習することとしました。

2 点目は単一の文字列を入力とした際の挙動の違いです。入力値が複数だった場合(Strings と同様の場合)は入力の型の指定が配列型(pyspark)と辞書型(sklearn)と異なるもののどちらも同様の処理をしていたのに対して、入力値が単一の文字列だった場合は異なる処理をしていました。pyspark では入力値は必ず配列でなければならないので要素数 1 の配列に直して入力していましたが、sklearn では文字列として受け取った後に 1 文字ずつに分解して辞書型にして処理しておりました。これについては pyspark にて sklearn と同様の処理を実装することとしました(この実装の段階で、pyspark での Accuracy が 0.8 程度まで改善しました)。

次に Random Forest の仕様について調査したところ、学習の際のデフォルトパラメータの設定が異なっておりました。特に最大の木の深さのデフォルト設定について、sklearn では無制限と設定されているのに対して、pyspark では 5 と設定されている点が大きく異なりました。これについては、pyspark の最大の木の深さが 30 以下しか設定できないことから、pyspark と sklearn の双方で 29 と設定して学習することとしました。

これらの仕様の違いを調整した後に再度学習すると下表の結果が得られたため、仕様の違いが結果に及ぼす影響を抑えられたと考えられます。

表 2 pysparkとsklearnにおいてFeature Hashingの仕様およびRandom Forestのパラメータを統一して学習した際のマルウェアの検知精度

また、この調整が終わった後に Strings を特徴次元数 20000 で Feature Hashing したものを学習させたところ、木の深さが増加したことにより学習に 75 分ほど時間がかかってしまったものの、Accuracy が 0.915 となったため当初期待されていた精度の実現を確認できました。

Stringsの統計情報を活用したマルウェア検知

基礎技術研究部のメンバーの方が過去に他のデータセットにおいて Endgame 社の公開しているemberで定義された Strings の統計情報(次元数 104、dense)を活用したマルウェア検知を試みたところ、Feature Hashing を用いたときよりも良い精度を実現できたとの話を聞き、本データセットでも同様の結果となるか検証しました。結果として以下の表が得られたため、本データセットにおいても特徴量の統計情報がマルウェア検知に有効であることが確認できました。しかし、Strings の統計情報は dense な特徴量であったこともあり、学習に 220 分もの時間がかかってしまいました。

表 3 Stringsを用いたマルウェア検知におけるFeature Hashingとember(統計情報)の精度

課題

分散処理基盤を構築した際にサンプルモデルで学習したときは pyspark でも 25 分程度で機械学習が行えていましたが、Random Forest のパラメータの修正や特徴量の定義などを工夫するに連れ機械学習にかかる時間が増大してしまった点が課題であると感じました。本インターンシップでは時間の関係上できませんでしたが、様々な特徴量を組み合わせて学習させたときの精度についても検証する必要があります。しかし、本インターンシップで最も良い精度であった ember の Strings を用いた機械学習において 220 分に及ぶ学習時間を必要としてしまっており、これに更に特徴量を加えて学習することを考えると膨大な時間が必要になってしまうことが想定されます。

この課題の解決のためには、Python でのコーディングによる Spark の利用(pyspark)ではなく Spark に適した言語である Scala を用いたコーディングや、そもそもデータの前処理だけ Spark で行い機械学習は Amazon Sagemaker に投げてしまうといった方法が必要となると考えられます。インターンシップ最終日は Scala を用いたコーディングでどの程度学習時間が変化するか確かめてみたいと考えています。

インターンシップの感想

本インターンシップを通して、初めて分散処理基盤での機械学習を行えたことがとても良い経験となりました。特に、pyspark によるデータの前処理や学習のプロセスを一通り体験することで、個人的な視点から見た分散処理基盤の利点や欠点について考えることができたことは、普段の授業や研究では体験できないものでした。私の力不足もあり様々な特徴量の組み合わせや、未だに取り入れることができていない特徴の活用などを行なえなかったことが心残りではありますが、逆に今後の自分自身の研究を通して追求したいと強く思える良いきっかけとなりました。

ハイライト

1--3日目:AWSのEMRを用いた分散処理基盤の構築

初めての AWS に戸惑いながらも、様々なノードの組み合わせによる環境構築を試しながら基盤を構築する。

3--5日目:StringsのFeature Hashingを用いたRandom Forestの学習

pyspark の data frame の扱いに四苦八苦しながら Strings の Feature Hashing の実装や、機械学習を行う。初めて分散処理で機械学習を行ったため新鮮な体験が得られた。

6--7日目:impfuzzyを用いた機械学習を通したpysparkとsklearnの仕様の調査

今まで機械学習は Python でしか行ったことがなかったため、pyspark の利用において同じようなモデルでも言語によってデフォルトパラメータの定義や、パラメータそのものの定義が異なることが意外だった。最初は pyspark と sklearn で大きく精度が異なったことに驚いたが、pyspark の各ライブラリの仕様を読み解くに連れ段々と原因がわかり、最終的には同程度の状態まで持っていくことができたため納得することができた。

7--9日目:emberのStringsを用いたRandom Forestの学習

ember で定義されている Strings の統計情報の抜き出し方を読み解き、pyspark に実装した。ここでも pyspark の data frame の操作方法やデータの外部出力などで手こずってしまったが、なんとか形にはすることができた。9 日目に半日間かけて学習させた際はどのような結果が出力されるかヒヤヒヤしたが、期待する結果が得られたことは幸いだった。

9日目:ブログの執筆

ブログの執筆を行いながら、本インターンシップの内容を振り返った。一通り振り返ってみると、やはり pyspark での機械学習にかかる計算時間や data frame の扱いづらさがネックだったように感じた。一方、分散処理の利点を生かした大容量データの加工に関しては、時間はかかるが比較的簡単に実現できたため便利に感じた。