パイソン加工

今日、怠け者だけが機械学習、ニューラルネットワーク、および人工知能について話すことはありません(書き込み、考えています)。 昨年、MLは10代のセックスと比較されました-誰もが望んでいますが、誰もそれを望んでいません。 今日、誰もがAIによって仕事がなくなることを心配しています。 最新のGartnerの調査によると、2020年までにAIのおかげで、排除されるよりも多くの仕事が創出されるため、落ち着くことができます。 だから、親愛なる友人、MLを教えてください、そしてあなたは幸せになります。













注:Hacker誌の記事の完全版の出版物シリーズを継続します。 著者のスペルと句読点が保存されました。







   ** ML- Python   Azure Functions**, ,      ,      . :)
      
      





この記事では、 Aktion-press (オンラインサブスクリプションサービス)で行ったプロジェクトの例など、実用的なケースでMLを紹介します。 この例の説明は、多くの人にとって役に立つと確信しています。 なぜそんなに多いのですか? はい、私たちが解決した問題は「膨大な数のメールのアドレスへのソートと転送」と呼ばれていたためです。 管理者が適切な部門に振り分けて転送しなければならない巨大な通信の問題はほとんど普遍的であり、この問題は現代の方法で解決されなければなりません。







そこで、顧客と相談した後、文字ソートの最大限の自動化のための機械学習モデルを開発することにしました。







機械学習モデル



このソリューションの言語としてPythonを選択したことに驚かないと思います。 これは歴史的に起こりましたが、高レベルであり、最も重要なのは、機械学習に役立つ多くのライブラリを備えていることです。 以下でそれらについてお話します。







正直なところ、この場合のMLについて特別なことはありません。 ロジスティック回帰に基づいた一連の単純なバイナリ分類子は有望な結果を示し、データの準備と埋め込みテキストの構築に焦点を当てて、モデル自体からある程度抽象化することができました。 しかし、リポジトリ自体はすでに他の3つの独立したプロジェクトの基盤として使用されており、いくつかの分類実験で十分に証明され、開発への迅速な移行のための信頼できる基盤として確立されました。 したがって、このセクションのタスクは「ノウハウ」を示すことではなく、次の運用化のセクションの基礎として必要です。







このコードを自分で試してみたり、再利用したりできるように、ここで私の経験を共有し、いくつかの推奨事項を示します。







機密性を維持するために、元のデータセットは、同様の公開されているマクドナルドのレビュー分類データセットに置き換えられました。 ファイルdata / data.csvを参照してください。







データ自体は、 Id



Text



、およびClass



3つの列を持つCSVファイルで提示されました。 また、NLTKはCSV形式のファイルからデータを読み取るための組み込みサポートを提供しないため、フォルダーからファイルを単一のデータフレームパンダとして読み取るか、NLTK形式の段落、文、単語などの形式でテキストを抽出できる独自のモジュールを作成しました。







そして、クライアントデータでCsvCorpusReader



を読み取るこの自己記述モジュールを初期化するためのコードをCsvCorpusReader



ます。 クラスの実装は、ファイルlib \ corpus.pyで確認できます。 Experiments \ TrainingExperiment.pyファイルの内容を理解することを強くお勧めします。







 #%% create corpus corpus = CsvCorpusReader(".\data", ["data.csv"], encoding="utf8", default_text_selector=lambda row: row["Text"])
      
      





初期化の最後に、文書から単語を抽出し、それらを正規化する必要があります。 私たちのケースでは、一連の実験の後、一連の補助関数をプロセス全体のラッパーとして使用して、使いやすい構成レベル内でNLTKおよびGensimライブラリの呼び出しを非表示にすることにしました。







以下に、抽出keep_levels=Levels.Nothing



に単語のリストの形式でドキュメントを返すコマンドを与え、段落または文の構造を破棄します( keep_levels=Levels.Nothing



参照)。 次に、各単語を小文字に変換し、ストップワードを破棄し、単語の基本を強調します。 最終段階では、これらが単なるタイプミスであるか、分類に重要な影響を与えないと仮定して、低頻度の単語を削除します。







以下のコードは英語のデータサンプルのみに焦点を当てていることに注意してください。オリジナルバージョンでは、ロシア語のより正確な分類を可能にするPyMorphy2を使用してロシア語の見出し語化が実装されました。







 #%% tokenize the text stop_words = ['would', 'like', 'mcdonald'] text_processor = generate_processor(keep_alpha_only=True, to_lower=True, stopwords_langs=['english'], add_stopwords=stop_words, stemmer_langs=['english']) docs_factory = lambda: corpus.words(keep_levels=Levels.Nothing, **text_processor) word_frequencies = Counter((word for doc in docs_factory() for word in doc)) min_word_freq = 3 docs = [ [ word for word in doc if word_frequencies[word] >= min_word_freq ] for doc in docs_factory() ]
      
      





エンクロージャーをトークン化するとすぐに、次のステップは投資を構築することです。 各ドキュメントを分類子で使用する一連の無意味な数値に変換するには、以下のコードが必要です。







いくつかの異なるアプローチ(BoW、TF-IDF、LSI、RP、およびw2vを含む)をテストしましたが、このケースでは500のトピックを抽出した従来のLSIモデルが最良の結果( AUC = 0.98)を提供しました。 最初に、コードは、共有フォルダーに既存のシリアル化されたモデルが存在するかどうかを確認します。 モデルがない場合、コードは事前に準備されたデータを使用して新しいモデルをトレーニングし、結果をディスクに保存します。 モデルが検出された場合、それは単にメモリにロードされます。 次に、コードはデータセットを変換し、次の添付ファイルでストリームを繰り返します。







効率の観点から、LSIモデルはword2vecやその他のより複雑なアプローチに基づいたはるかに強力なvector2アルゴリズムを上回っています。これはいくつかの考えられる理由による可能性があります。







最も明白なのは、探していたタイプの文字には、自動返信の場合のように、予測可能な繰り返しパターンの単語があったことです(たとえば、 「ありがとうございます。私は...質問が緊急の場合はオフィスにいません... " )。 したがって、処理には、TF-IDFなどの単純なもので十分です。 LSIは一般的なイデオロギーをサポートしており、このモデルは処理に適した同義語を追加する方法と見なすことができます。 同時に、Wikipediaでトレーニングされたword2vecアルゴリズムは、複雑な同義語構造のために不要なノイズを生成する可能性が高く、それによってメッセージ内のテンプレートが「ぼやけ」、したがって分類の精度が低下します。







このアプローチは、word2vecやリカレントニューラルネットワークの時代であっても、古くてかなりシンプルな方法を試す価値があることを示しました。







 #%% convert to Bag of Words representation dictionary_path = os.path.join(preprocessing_path, 'dictionary.bin') if os.path.exists(dictionary_path): dictionary = corpora.Dictionary.load(dictionary_path) else: dictionary = corpora.Dictionary(docs) dictionary.save(dictionary_path) docs_bow = [dictionary.doc2bow(doc) for doc in docs] nested_partial_print(docs_bow) #%% convert to tf-idf representation tfidf_path = os.path.join(preprocessing_path, 'tfidf.bin') if os.path.exists(tfidf_path): model_tfidf = models.TfidfModel.load(tfidf_path) else: model_tfidf = models.TfidfModel(docs_bow) model_tfidf.save(tfidf_path) docs_tfidf = nested_to_list(model_tfidf[docs_bow]) #%% train and convert to LSI representation lsi_path = os.path.join(preprocessing_path, 'lsi.bin') lsi_num_topics = 500 if os.path.exists(lsi_path): model_lsi = models.LsiModel.load(lsi_path) else: model_lsi = models.LsiModel(docs_tfidf, id2word=dictionary, num_topics=lsi_num_topics) model_lsi.save(lsi_path) docs_lsi = model_lsi[docs_tfidf]
      
      





いつものように、義務的なルーチンコードを取り除くことは不可能です。 さらに、skit-learnを使用して機械学習用のデータを準備するときに役立ちます。







上記で述べたように、1つのマルチクラス分類子の代わりに複数のバイナリを使用します。 そのため、クラスの1つに対してバイナリターゲットを作成します(このサンプルではSlowServiceです)。 class_to_find



変数の値を変更し、以下のコードをclass_to_find



実行して、各クラス分類子を個別にトレーニングできます。 評価スクリプトは、いくつかのモデルで動作するように設計されており、選択したフォルダーからそれらを自動的にダウンロードします。 最後に、トレーニングとテストのデータセットが形成され、ギャップのある行は完全に除外されます。







 #%% create target class_to_find = "SlowService" df["Target"] = df.apply(lambda row: 1 if class_to_find in row["Class"] else 0, axis=1) df.groupby(by=["Target"]).count() #%% create features and targets dataset features = pd.DataFrame(docs_features, columns=["F" + str(i) for i in range(lsi_num_topics)]) notnul_idx = features.notnull().all(axis=1) features = features[notnul_idx] df_notnull = df[notnul_idx] target = df_notnull[["Target"]] plot_classes_scatter(features.values, target["Target"].values) #%% split dataset to train and test train_idx, test_idx = train_test_split(df_notnull.index.values, test_size=0.3, random_state=56) df_train = df_notnull.loc[train_idx] features_train = features.loc[train_idx] target_train = target.loc[train_idx] df_test = df_notnull.loc[test_idx] features_test = features.loc[test_idx] target_test = target.loc[test_idx]
      
      





次に、分類子のトレーニングを開始し(この場合、これはロジスティック回帰です)、モデルを以前に変換を埋め込むために使用したのと同じ一般ディレクトリに保存します。







ご覧のとおり、以下のコードでは、モデル名の特別な形式: class_{0}_thresh_{1}.bin



を遵守しています。 これは、さらに評価する際にクラス名と対応するしきい値を決定するために必要です。







そして、続行する前の最後の注意点。 開発ツールとして、Visual Studio Codeを選択しました。 Pythonのような動的言語のための基本的なIntelliSense機能(コード補完とツールチップ)を提供する使いやすい軽量エディターです。 同時に、 JupyterおよびPython拡張機能とIPythonコアを組み合わせることで、コードを1行ずつ実行し、スクリプトを再起動せずに結果を視覚化できます。これは、MLタスクに常に便利です。 はい、標準のJupyterに似ていますが、IntelliSenseおよび/ gitコードの向きがあります。 少なくともサンプルで作業している間は試してみることをお勧めします。生産的な開発には、VS Codeに関連する他の多くの機能があるためです。







以下のコードについては、 ROCしきい値プロットする行はJupyter拡張機能の使用例です。 Run cell



上にある特別な「セルをRun cell



ボタンをクリックして、TP値とFP値を確認し、それらを右側の結果ペインのしきい値と比較できます。 データセットの顕著な不均衡により、最適なカットオフレベルは通常の0.5ではなく常に約0.04であったため、運用中にこのチャートを積極的に使用しました。 テストにVSコードを使用できない場合は、標準のPythonツールを使用してスクリプトを実行し、別のウィンドウで結果を表示した後、ファイル名を直接変更できます。







 #%% train logistic classifier classifier = LogisticRegression() classifier.fit(features_train, target_train) #%% score on test scores_test = classifier.predict_proba(features_test)[:, 1] #%% plot ROC threshold values pd.DataFrame(nested_to_list(zip(tsh, tp_test, fp_test, fp_test-tp_test)), columns=['Threshold', 'True Positive Rate', 'False Positive Rate', 'Difference']).plot(x='Threshold') plt.xlim(0, 1) plt.ylim([0,1]) plt.grid() plt.show() #%% save model threshold = 0.25 model_filename = 'class_{0}_thresh_{1}.bin'.format(class_to_find, threshold) joblib.dump(classifier, os.path.join(model_path, model_filename))
      
      





それでは、評価スクリプトの時間です: Score \run.py。 その中にはほとんど新しいものはありません。コードの大部分は、前述の初期トレーニング実験から取得したものです。 GitHubリポジトリでこのファイルの内容を確認してください。







CSVファイルは評価のために入力に送信され、出力では2つの異なるファイルを取得します。1つは評価されたクラスを含み、もう1つは評価できない行識別子を含みます。 ファイルを使用する理由については、後で運用化について説明するときに説明します。







このセクションの最後で、1つのマルチクラス分類子の代わりに複数のバイナリを使用する理由を説明します。 まず、作業を開始してクラスのパフォーマンスを個別に最適化することが非常に簡単でした。 このアプローチでは、自動応答の場合のように、クラスごとに異なる数学モデルを使用することもできます。自動応答は、多くの場合、かなり硬直した構造を持ち、単純な単語の袋を使用して処理できます。 同時に、ITスペシャリストの観点からは、以下のコードのようなものを使用すると展開が簡単になり、他のモデルに影響を与えずに新しいモデルを接続したり、既存のモデルを変更したりできます。







 model_paths = [path for path in os.listdir(os.path.join('..', 'model')) if path.startswith('class_') ] for model_path in model_paths: model = joblib.load(os.path.join('..', 'model', model_path)) res = model.predict_proba(features_notnull)[:, 1] class_name = model_path.split('_')[1] threshold = float(model_path.rsplit('.', 1)[0].split('_')[-1]) result.loc[:, "class_" + class_name] = res > threshold result.loc[:, "class_" + class_name + "_score"] = res
      
      





ローカルPCから独自のデータを使用して、操作をまったく行わずに、今すぐコードを試すこともできます。









VS Codeでは、 デバッグデバッグセクション(Ctrl + Alt + D)を開き、構成として[ スコア(Python) ] 選択し、[ デバッグの開始 ]をクリックして、エディターで行ごとのコード分析を実行することもできます。 アルゴリズムが終了すると、結果はフォルダーScore \ debugのファイルinput.scores.csvおよびinput.unscorable.csvにあります。







運用化



Azure FunctionsでのPythonサポートはまだ初期のプレビューであるため、ミッションクリティカルなタスクに使用することは望ましくありません。 しかし、多くの場合、MLはそのようなものには適用されないため、実装の容易さは、予備バージョンの適合に関する困難を上回る場合があります。







したがって、この段階では2つのスクリプトがありました。 Experiments \ TraintExperiment.pyスクリプトはモデルをトレーニングし、変換されてトレーニングされたモデルを共有ディレクトリに保存します。このトレーニングスクリプトは、必要に応じてローカルマシンで再起動されると想定されています。 Score \ run.pyスクリプトは毎日実行され、新しいメールが到着するとソートされます。







このセクションでは、Azure Functionsを使用したプロセスの運用化について説明します。 これらの関数は使いやすく、スクリプトをさまざまなトリガー(HTTP、キュー、blobストレージオブジェクト、WebHookなど)にバインドし、いくつかの自動出力バインディングを提供し、安価です。消費プランを選択すると、支払いは0.000016だけです1秒間に使用されるRAMのギガバイトごとに1ドル。 ただし、制限があります:関数は10分以上実行できず、1.5 GBを超えるRAMを使用します。 これが自分に合わない場合は、App Serviceに基づく特別な料金プランにいつでも切り替えることができますが、サーバーレスアプローチの他の利点へのアクセスは維持されます。 ただし、単純なロジスティック回帰および数百文字のパッケージの場合、選択した計画が最適でした。







プログラマーの観点から見ると、関数は関数自体の名前(この場合は単にScore )を保持するフォルダーであり、2つの異なるファイルが含まれています。









Function.jsonは、手動で作成するか、Azureポータルを使用して構成できます。 この場合に受け取ったコードを以下に示します。 最初のバインディングinputcsvは、 mail-classify/input/{input_file_name}.csv



一致する名前のファイルがデフォルトのAzure BLOBストアに表示されるたびにスクリプトを実行します。 残りの2つのバインディングは、関数が成功した後に出力ファイルを保存します。 この場合、それらを別の出力フォルダーに保存します。それらの名前は、接尾辞がscoredまたはunscorableである入力ファイルの名前に対応します。 したがって、GUIDなどの任意の識別子名を持つファイルをinput



フォルダーに配置できます。GUIDから派生した名前を持つ2つの新しいファイルが、しばらくするとoutput



フォルダーに表示されます。







 { "bindings": [ { "name": "inputcsv", "type": "blobTrigger", "path": "mail-classify/input/{input_file_name}.csv", "connection": "apmlstor", "direction": "in" }, { "name": "scoredcsv", "type": "blob", "path": "mail-classify/output/{input_file_name}.scored.csv", "connection": "apmlstor", "direction": "out" }, { "name": "unscorablecsv", "type": "blob", "path": "mail-classify/output/{input_file_name}.unscorable.csv", "connection": "apmlstor", "direction": "out" } ], "disabled": false }
      
      





Azure関数のrun.py



スクリプトは、最初の「非操作化」バージョンとほぼ同じです。 唯一の変更は、関数が着信および発信データストリームを渡す方法に関するものです。 選択された入出力データのタイプ(HTTP要求、キュー内のメッセージ、BLOBファイル...)に関係なく、内容は一時ファイルに保存され、そのパスは対応するバインディングの名前で環境変数に書き込まれます。 たとえば、この場合、関数が実行されるたびに、「 ... \ Binding [GUID] \ inputcsv 」という名前のファイルが作成され、このパスはinputcsv環境変数に保存されます 。 同様の操作が各発信ファイルに対して実行されます。 このロジックを考慮して、スクリプトにいくつかの小さな変更を加えました。







 # read file input_path = os.environ['inputcsv'] input_dir = os.path.dirname(input_path) input_name = os.path.basename(input_path) corpus = CsvCorpusReader(input_dir, [input_name], encoding="utf8", default_text_selector=lambda row: row["Text"]) [...] # write unscorables unscorable_path = os.environ['unscorablecsv'] ids_null.to_csv(unscorable_path, index=False) # pandas DataFrame [...] # write scored emails output_path = os.environ['scoredcsv'] result.to_csv(output_path) # pandas DataFrame
      
      





これらはすべて、CSVファイルがBLOBストレージに表示され、その結果として予測を含むファイルを受信したときにサービスを開始するために必要な変更です。







正直に言うと、他のトリガーをテストしましたが、最も強力なPython関数(モジュール)がサーバーレスシステムの呪いになることがわかりました。 Pythonのモジュールは、他の多くの言語のように接続する必要がある静的ライブラリではありませんが、起動するたびに実行されるコードです。 サービスなどの長期的なソリューションの場合、これはほとんど目に見えませんが、Azureの機能の観点からは、毎回スクリプトを完全に実行するにはかなりのコストがかかります。 これにより、PythonでのHTTPトリガーの使用が複雑になりますが、多くのMLスクリプトで一般的なCSVファイルベースのバッチ処理により、データ行あたりのこれらのコストを合理的な最小値まで削減できます。







Pythonでリアルタイムトリガーなしで実行できない場合は、専用のAzure App Serviceの料金プランに切り替えることができます。これにより、ホストのコンピューティングリソースが大幅に増加し、インポートが高速化される可能性があります。 この場合、実装の容易さと消費計画の低コストが、迅速な実装の利点を上回りました。







続行する前に、Visual Studio Codeを使用して開発を簡素化する方法を見てみましょう。 この記事の執筆時点では、 Functions CLIはPythonテンプレートの初期生成を提供していましたが、デバッグ機能はありませんでした。 ただし、VS Codeの組み込み関数を使用して、ランタイムをシミュレートすることはそれほど難しくありません。 .vscode \ launch.jsonファイルは、デバッグオプションを構成するのに役立ちます。 JSON , debug Score (Python) VS Code ${workspaceRoot}/Score/run.py



${workspaceRoot}/Score



, , - . , Azure Functions ( ). Debug (Ctrl + Alt + D) VS Code, Score (Python) Start Debugging , .







 [...] { "name": "Score (Python)", "type": "python", "request": "launch", "stopOnEntry": true, "pythonPath": "${config:python.pythonPath}", "console": "integratedTerminal", "program": "${workspaceRoot}/Score/run.py", "cwd": "${workspaceRoot}/Score", "env": { "inputcsv": "${workspaceRoot}/Score/debug/input.csv", "outputcsv": "${workspaceRoot}/Score/debug/output.csv", "unscorablecsv": "${workspaceRoot}/Score/debug/unscorable.csv" }, "debugOptions": [ "RedirectOutput", "WaitOnAbnormalExit" ] } [...]
      
      





Jupyter , . , . IPython, Debug .







 if "IPython" in sys.modules and 'Score' not in os.getcwd(): os.environ['inputcsv'] = os.path.join('debug', 'input.csv') os.environ['scoredcsv'] = os.path.join('debug', 'input.scores.csv') os.environ['unscorablecsv'] = os.path.join('debug', 'input.unscorable.csv') os.chdir('Score')
      
      







, , Azure. Python Azure , . Python 2.7. 3.6, wiki Python ( ) D:\home\site\tools . . Python 2.7 PATH python.exe .







Kudu, , , . setup , . , 3.6, , (.zip) Python D:\home\site\tools .







 tools_path = 'D:\\home\\site\\tools' if not sys.version.startswith('3.6'): # in python 2.7 import urllib print('Installing Python Version 3.6.3') from zipfile import ZipFile if not os.path.exists(tools_path): os.makedirs(tools_path) print("Created [{}]".format(tools_path)) python_url = 'https://apmlstor.blob.core.windows.net/wheels/python361x64.zip' python_file = os.path.join(tools_path, 'python.zip') urllib.urlretrieve(python_url, python_file) print("Downloaded Python 3.6.3") python_zip = ZipFile(python_file, 'r') python_zip.extractall(tools_path) python_zip.close() print("Extracted Python to [{}]".format(tools_path)) print("Please rerun this function again to install required pip packages") sys.exit(0)
      
      





pip. Pip API Python, Python , . , Python ( langid , pymorphy ) , . , C++. App Service Visual C++, (wheels). pip ( ), ML- wheel . Azure Blob Storage, Azure. .







 def install_package(package_name): pip.main(['install', package_name]) install_package('https://apmlstor.blob.core.windows.net/wheels/numpy-1.13.1%2Bmkl-cp36-cp36m-win_amd64.whl') install_package('https://apmlstor.blob.core.windows.net/wheels/pandas-0.20.3-cp36-cp36m-win_amd64.whl') install_package('https://apmlstor.blob.core.windows.net/wheels/scipy-0.19.1-cp36-cp36m-win_amd64.whl') install_package('https://apmlstor.blob.core.windows.net/wheels/scikit_learn-0.18.2-cp36-cp36m-win_amd64.whl') install_package('https://apmlstor.blob.core.windows.net/wheels/gensim-2.3.0-cp36-cp36m-win_amd64.whl') install_package('https://apmlstor.blob.core.windows.net/wheels/nltk-3.2.4-py2.py3-none-any.whl') install_package('langid') install_package('pymorphy2')
      
      





. , , NLTK. install_packages.







 import nltk; nltk_path = os.path.abspath(os.path.join('..', 'lib', 'nltk_data')) if not os.path.exists(nltk_path): os.makedirs(nltk_path) print("INFO: Created {0}".format(nltk_path)) nltk.download('punkt', download_dir=os.path.join('..', 'lib', 'nltk_data')) nltk.download('stopwords', download_dir=os.path.join('..', 'lib', 'nltk_data'))
      
      





Setup , . , : , Python 3.6, , .







おわりに



, , Azure Functions ML- Python. , ML . GitHub .







これはハッカー誌の記事の完全バージョンであることを思い出します。








All Articles