ABEJA Platformを利用してモデル学習(Abeja Platform試用報告 第2弾)

こんにちは、GliaComputing研究員の時田です。

普段、機械学習モデルの学習にどのような環境を使われていますか?
モデルの学習環境としてはGPUや各種のミドルウェアの用意が必要であり、 一通り揃えるまでにも、また、メンテナンスにもコストがかかりますね。 そのような背景の中、AWSやGCP, Azureなどのクラウドベンダーが 機械学習向けのPaaSに力を入れているようです。

本記事では、機械学習向けPaaSの一つである「ABEJA Platform」を使って、 データセットの作成から学習、そして、学習済みモデルのWebサービス化までを 通してみました。 本記事はその試用レポートです。

第三者として、ABEJA Platformを利用して見た感想をまとめていこうと思います。
なお弊社では、株式会社ABEJA様のご協力により評価用ライセンスを発行していただいております。 本記事はそのライセンスの範囲で試行したものです。

はじめに

ここ数年のブームの影響もあり、実際に機械学習に取り組んでいる方、多いと思います[1]このブログを読んでいらっしゃるということは、何らかの形で機械学習の取り組みに触れている方だと想像します。。 機械学習に取り組む環境としては、TensorflowなどのフレームワークやGPUを扱うためのミドルウェアが整備され、実装するだけならその気があれば誰でもできる状況にありますね。

しかし、効率的に機械学習の実験に取り組むためには計算機環境の整備が重要です[2]最近の機械学習アルゴリズムの主流は、モデル構造が複雑(非線形)で、 … Continue reading。高速な計算機と各種ソフトウェアの整備は、時間的にも費用的にもコストがかさみます[3]バージョンの不整合でライブラリが動かないなんて経験みなさんもありますよね?

そのような中、AWS, GCP, Azureなどのクラウドベンダーが揃って機械学習用のPaaSを提供するようになってきており、ハードの用意や管理についてはだいぶ楽ができるようになってきています。今回は、そのような機械学習用PaaSの中で、株式会社ABEJAが提供する「ABEJA Platform」を利用して、モデルの学習とWebサービスとして公開するまでの試用レポートをまとめていきます。

本記事は、以前書いたABEJA Platformを利用してWebサービスを公開する手順をまとめた記事(下記記事)の続編となります。データの収集からモデルの学習、そしてWebサービスの公開までを通した試用レポートとなります。

本記事は、2章でABEJA Platformの概要と使ってみて良いと思った点/足りないなと感じた点をまとめます。3章以降では具体的にモデルの学習とWebサービスとしての公開までの詳細を記載していますので、概要を知りたい方は2章まで読んでいただけたらと思います。3章では具体的にどのようなタスクに取り組むのかを紹介し、4章で具体的にABEJA Platformを使ってモデルの学習から公開までの手順を紹介します。実際に今回作成したWebサービスについては5章をご覧ください。6章でまとめとなります。

ABEJA Platform

「ABEJA Platform」とは、機械学習用のPaaSとして株式会社ABEJAが提供するサービスです。 他のPaaSと同様に、GPUや各種のフレームワーク(Tensorflow, Chainer等)を備えた環境を利用することができます。 ABEJA Platformについて詳しく知りたい方は以下の公式Webをご覧ください。

また、当ブログの以前の記事も合わせてご覧いただけたらと思います。今回は、データの管理と学習部分に注目して試用した結果の報告となります。

ABEJA Platformの特徴

一般に機械学習モデルの開発は下図1の手順で進むと思います。1~5のプロセスは一直線で進むだけでなく、実験をする中で相互に行き来します[4]問題を見つけて何度も繰り返すということは機械学習プロジェクトに於いて非常に重要です

図1. 機械学習モデルの開発プロセスの概略

ABEJA Platformは公式Webにある通り、これらのプロセスを容易にし、機械学習モデルの開発者がアルゴリズム開発(図1の②)に注力できるようになることを目指しているようです。特に、データの収集/管理からデプロイまでを通せるということと、検証サイクルを増やせるということを特徴とされています(図2、公式Web参照)。

図2. ABEJA Platform(https://abejainc.com/platform/ja/より)

また、ストレージ、CPU、GPUなどの計算環境も当然用意されています。

利用してみて感じたABEJA PlatformのPros./Cons.

ここでは、ABEJA Platformを利用してみて長所/短所と感じた点を列挙します。公式Webなどを拝見したところ、ABEJA Platformは図1の①、③、⑤に少なくとも現時点では注力しているサービスであると感じます。今後拡充していく部分は当然あると思いますが、これらの点に注目して現時点での使い勝手を見ていこうと思います。なお、Webサービスとして公開する部分については前回の記事で触れているので、今回はその部分にはあまり触れないつもりです。

次章から、具体的に実装した手順を紹介しながらこれらの点について詳しく紹介していきます。

Pros.

  • アノテーションサービスとの連携(図1の①)
    • ABEJAさんと直接契約しているアノテータの方々にアノテーション作業を依頼することができます。そのため、高品質なアノテーションデータが期待できます(実際に試してはいないのですが、話を聞く限りとても期待できそうです)。
  • ソースコード、学習済みモデル、データセットがひとまとめで管理できる(図1の③)
    • 機械学習モデル開発では、データ、モデル構造、ハイパーパラメータを試行錯誤することが必要になります。そのため、これらに加えて学習済みモデルを一纏めで管理していく必要があります[5]管理が雑だとどんなデータ、パラメータで学習したモデルがわからなくなり、効率的に実験を進められないですよね
    • ABEJA Platformでは、学習を実行するとこれらを自動的に一纏めで管理することができますので、いつどの実験を行ったかが容易に確認できます。
    • 機械学習モデルの開発を効率的に進めるには、この管理の問題が重要であり、この点は良く考えていらっしゃるなと感じます。
  • デプロイが容易、モデルの切り替えがシームレスに行える(図1の⑤)
    • 前回の記事に記載していますが、WebAPIとして公開したり、エッジデバイスのデーモンとしてのデプロイが管理コンソールを使って視覚的に実行できます。
    • データやハイパーパラメータを変えて学習したモデルを即座に実サービスに適用できます。この点は特別な技術が無くても容易に実現でき、Ops側の方が試行できるため変化に適応するのが容易ではないか感じます。

Cons.

  • 独自のデータセットを使う場合、手順が整備されていない(図1の①)
    • アノテーション作業をABEJA Platformで行えば、データを作成するのは容易とのことですが、独自のデータセットをimportしようとすると結構大変でした。
    • まだドキュメントが整備されていない部分であり、ここは今後整備されるものと思います。 ← 本記事作成中に手順をQiitaの方にまとめてくださいました!こちらを参照ください。
  • デバッグ環境が弱い(図1の②)
    • デバッグは正直やりにくいです。ローカル環境でソースコードを作成し、ローカルで数iterationの動作確認するのが最も簡単な手順と思います。
    • こちらの記事にあるようにPyCharm(有料版)を利用することでデバッグが容易になるようではあります。
    • また、CLIを利用してlocalで学習ができるようになっており、そこで作成されたDockerコンテナに入ってデバッグするという手もあります。
    • (2019/05/30追記)Visual Studio CodeがDockerコンテナ内のリモートデバッグ機能をサポートしたということで、VS Codeを使ったデバッグについて解説記事を書いていただきました!こちらを参照ください。
  • モデルの評価がやりにくい(図1の④)
    • 学習までは容易ですが、学習後のモデルを使って、validationデータで精度評価したり、学習時に交差検証するなどはサポートされていないようです。
    • jupyter notebookが使えるので、独自に実装していけば何でもできそうですが、手順の整理をしていただけると助かるなと思います。

Cons.については、記事作成時点(2019年4月)で我々が触ってみたところの感想です。現在も開発は進んでいますし、サンプルコードやドキュメントの整理も日々進んでいます。デバッグ、評価の部分については今後サポートしていく予定と担当の方からも伺っていますので、今後の公式リリースに注目ください。

課題設定

課題設定

ABEJA Platformを利用した学習の題材として、弊社社員[6]私のことなんですけどねが個人で運用しているWebサービスであるChocoball Detectorを扱います。

ChocoballDetector

一箱にチョコボールが何個入っているのか気になることってありますよね? しかし、一つ一つ数えるのは面倒です。 そこで、チョコボールの写真からチョコボールを認識することで自動で個数を計測するためのシステムが「ChocoballDetector」です。

図3. ChocoballDetectorのイメージ. チョコボールを写した画像をPOSTすると, チョコボールとパッケージを認識してチョコボールの個数を計測できる.

これまでは、物体認識のモデルにFaster R-CNN[1]を利用していたのですが、 今回はこれをSSD(SingleShotMultiboxDetector)[2]に 置き換えることを目標とします。

なお、ChocoballDetectorについての詳細は、以下の記事を参照ください。

作業手順

チョコボールを検出するためには、 チョコボールを撮影した画像を使ってモデルを学習する必要があります[7] … Continue reading。 また、学習したモデルは活用できなければ意味ないですよね?ということで、Webサービスとして運用したいです。 モデルを学習し、Webサービスとして運用するために、以下の手順を踏みます。

  • ChocoballDetectorのデータ収集
  • データのインポートとDataSetの作成
  • 学習
  • 運用

この手順はChocoboallDetectorに特化したものではなく、機械学習モデルを学習し公開するまでの一般的な手順です。 次章では、ABEJA Platformを利用した場合にそれぞれの作業が具体的にどのように進められるのかを詳しく紹介していきます。

モデルの学習からWebサービス公開まで

モデルの学習からWebサービス公開まで

この章では、ABEJA Platformを利用してモデルを学習し、公開するまでの手順を紹介します。

データの用意

今回、このような画像データを複数枚用意します。これらの画像は私が日々チョコボールを計測するなかで収集しているものです。

図4. 学習データの例.今回はこのような画像を22枚用意しました.

物体認識をするためには、アノテーションが必要です。 ABEJA Platformにはアノテーションツールが備えられており、また、ABEJA Platform内のアノテーションツールを使うことで、この後のデータセットの作成が容易になります。

しかし今回は、独自にPascal VOC形式のxmlファイルでアノテーションデータを用意し、 このデータをABEJA Platformへimportして使ってみます。 既にアノテーションデータを持っているとして、それをどうやって使っていくかという内容です。

ちなみに、アノテーション作業はLabelImgというツールを利用させていただきました。

ABEJA Platformへのデータimport

ABEJA Platformを使ってモデルの学習をするために、 手元にあるデータをアップロードします。 そして、アノテーションデータとアップロードした画像データを紐付けてDataSetを作ります。

画像データのupload

チュートリアルに従って作業を進めます。 ここでは、チュートリアルの通りCLIで作業を進めることとしますが、 同じ作業はコンソール画面でも行うことができます。 なお、特定のディレクトリに学習用の画像ファイルが格納されており、また、 アノテーションデータがPascalVOC形式のxmlファイルとして用意されているとします。

まずは、画像データのアップロード先チャンネルを作成します。

$ abeja datalake create-channel --name "chocoball_image" --description "images of chocoball and package" 

次に、作成したチャンネルに対して画像データをuploadします。 なお、画像ファイルが置いてあるディレクトリを’image_dir’とします。 以下のコマンドで、image_dirの直下にあるファイルのABEJA Platformへのアップロードが始まります。

$ cd {image_dir}
$ abeja datalake upload --channel_id {channel_id} --recursive .

アップロードが終了すると、ABEJA Platformの管理コンソールからデータを確認することができます。

図5.ABEJA Platformのデータレイクにデータがアップロードされた様子. ファイル名をクリックするとブラウザで画像を確認することができる.

Datasetの作成

次に、Datasetを作成します。 Datasetとは、ABEJA Platformのデータ表現で、 データとアノテーションされたメタデータのセットになります(言葉通りですね)。 そして、各データファイルとメタデータの組をDatasetItemと呼んでいます。詳しくはドキュメントを参照ください。

実は今回の作業のなかでここが一番大変なところでした。 ABEJA Platformの中でデータを取得し、ABEJA Platformの中のアノテーションツールでアノテーションをする方法については、 ABEJAの中の人が書いたブログに記載があります。 しかし、アノテーションデータまで持っている中でそれを使って学習するという方法についてはまだマニュアルが整備されていないようで、サンプルコード(これとかこれ)を参照しながら作る必要があります[8]概念がわかってしまえば、難しいことは特に無かったです。アノテーション済みのデータをABEJA Platformにアップロードする手順については、こちらの記事を参照ください。

今回作成したソースコードは以下のリンクより確認いただけます。

import_dataset_abeja.py – https://gist.github.com/tok41/2903135861e738c182ef4d60e0fcff1b

Dataset作成のポイントは3点です。 一つ目は、検出対象のカテゴリの登録です(86~100行)。今回は独自の物体クラスを学習させたいので、クラスの定義ファイルを読み、認識するクラスのリストを作成します。そして、検出対象のカテゴリ名とIDを設定し変数(props)にまとめます。

2つめに、今回は物体認識タスクなので、typeを“detection”にしたDatasetの作成を行います(102~110行)。create_datasetメソッドでDatasetを作成する際に、typeの指定と、カテゴリの設定(props)を入力します。

3つめに、画像内のオブジェクト毎にメタデータ(各オブジェクトの座標)を読み込み、メタデータのリストを使ってDatasetItemを作成します(122~152)。
対象画像毎にxml形式で用意するアノテーションデータを読み込みます(123~129行)。そして、物体ラベル、ラベルID、座標情報を辞書にまとめ、物体毎のリストを作成します(131~143)。画像毎に、メタデータのリストをDatasetItemとして登録します(148~152)。DatasetItemの登録部分については、ドキュメントが整備されておらず(見つけられていないだけ?)、サンプルコードをそのままコピーしてきています。

このPythonコードを実行することで、データセットの作成が完了します。問題なくDatasetを作成できると、ABEJA Platformの管理コンソールから、アノテーション済みのデータセットを確認することができます(図5)。

図5. アノテーションが付与されたデータを確認することができる. 今回はDetectionタイプのデータセットのため,オブジェクト毎にバウンディングボックスが表示されている.

モデル学習

次に、チョコボールを検出するモデルを学習します。3章に記載の通り、モデルにはSSDを利用します。また、SSDの学習にはchainercvを使います。

学習済みモデルのコピー

学習に利用するデータは用意できた量がかなり少ないため、学習済みモデルの重みをコピーしてfinetuningします。 以下のコードでPASCAL VOC 2007と2012の学習済みモデルから重みをコピーしたモデルを用意します。 (この部分はABEJAリサーチャーの藤本さんから教えてもらいました。ありがとうございます!)

from chainercv.datasets import voc_bbox_label_names
from chainercv.links import SSD300
from chainer.serializers import save_npz

model = SSD300(n_fg_class=len(voc_bbox_label_names),
               pretrained_model='voc0712')
model._children.remove('multibox')
save_npz('ssd300_voc0712_2017_06_06_extractor.npz', model)

これで、ssd300_voc0712_2017_06_06_extractor.npzというファイル名でモデルファイルが作成できます。

学習コードの作成

続いて、学習用のコードを作成します。実際に学習に利用したコードは以下のGistから確認してください。

train.py – https://gist.github.com/tok41/bfd2e5c6bcd8501886276c91058c008c

上記の学習コードは、ABEJA Platform sampleschainercvのexampleを参考に作成しました。前回の記事でも指摘した通り、ABEJA Platformは現在のところDockerをベースにしており、また、デバッグをサポートするツール等が無い状態です。 そのため、コードが動くかどうかをローカルでデバッグできるようmain関数を追加しています[9]この記事にあるように有料版のPyCharmを使えばデバッグはできるみたいです。ローカルで数イテレーションだけ実行し、とりあえず動くことを確認した後に、ABEJA Platformで学習を行います。

学習コードのポイントは2点です。
1点目は、handler関数の実装です。ABEJA Platformでは、学習ジョブを定義して実行する際に、handler関数を実行します(関数名は任意の名前をつけられる)。そのため、学習処理の全てはhanndler関数に書く必要があります。上記の私のコードでは、main関数を呼ぶようにしており、実際の学習処理はmain関数に書いています。

2つ目のポイントは、ABEJA Platformにあるデータセットの利用方法についてです。ABEJA Platformからデータを利用するために、以下のスクリプトを作成しています(ABEJA Platform samplesのコードをほぼそのままコピー)。

dataset.py – https://gist.github.com/tok41/8315c82e1391f02be36118386acc718b

train.pyの147~148行でDatasetを取得します。取得したデータセットを学習と評価用のデータに分割します(150~155)。そして、174~181行で学習と評価のイテレータを作成します。他は、chainercvでの学習コードをほぼそのまま利用しています。なお、load_dataset_from_api、DetectionDatasetFromAPIについては、こちらの記事を参照いただけたらと思います。

学習用のコードが準備できたら、学習に利用する一式をzipにまとめて、学習を実行するためのjob定義をします。 ここからは、チュートリアルに基づいて操作すれば良いです。

学習ジョブの作成

ABEJA Platformの管理コンソールにログインし、 Trainingメニューから「Job Definition」を選択します。 「Create Job Definition」ボタンで新規でジョブ定義を作成できます。

次に、「CreateVersion」ボタンで実際の学習jobを作成します。モデル構造を変えたり、パラメータを変えたり、データセットを変えるなど、学習をやり直す場合にも、新しいバージョンを作成します。 学習後のモデルは、この学習ジョブとバージョンで一意に特定できます。 このように、ソースコード、モデル、データセットをバージョン管理できるため、学習結果を後で確認することがやりやすくなるということです。

ここで、学習コード内で環境変数を呼ぶように書いておけば、学習ジョブの定義時に環境変数の値を指定できます。 これで、イテレーション数などをハードコードする必要がなくなり、都度ソースコードを作成する手間が省けます(図6)。

図6. モデル学習のジョブ定義のバージョンを作成する. 環境変数,ソースコード(zipファイル),データセットを一まとめとしてバージョンを定義.

学習ジョブの実行

ジョブ定義画面の「Jobs」をクリックすると、学習ジョブ一覧画面に移動します。 ここで、「Create Training Job」を選択して、実際のTrainingJobを作成します。

図7.学習ジョブの作成.

学習ジョブの作成が完了すると、学習が始まります。

図8.ジョブの作成を完了すると学習が始まる.ステータスがPendingからRunningと変わり,学習が正常終了するとSuccessに変わる.

学習の進行状況は、学習ジョブ画面の「Tensorbord」を選択することで、TensorbordでAccuracy、Lossの学習曲線を確認することができます。

図9.tensorbordで学習の状況を確認できる

学習結果のモデルファイルは図8の「Result」ボタンからダウンロードすることができます。 これで、ローカルで推論の実行ができるようになります。

Webサービスとして公開

上記で作成したモデルを利用してWebAPIを作成し公開します。 WebAPIの作成については、先の記事に記載の通りなので、 本記事ではポイントを絞って説明します。

推論コードの作成

推論のためのコードを作成します。コードのデバッグはモデルファイルをダウンロードして、ローカルで実行するのが良いと思います。作成したコードは以下の通りです。前回の記事で作成したものと変化はほぼ無いです。前回はFaster R-CNNを利用していたものを、chainercvのSSDを利用している点が主な差分です。

import os
import numpy as np
import chainer
from chainercv.datasets import voc_bbox_label_names
from chainercv.links import SSD300

ABEJA_TRAINING_RESULT_DIR = os.environ.get('ABEJA_TRAINING_RESULT_DIR', '.')

pretrained_model = os.path.join(ABEJA_TRAINING_RESULT_DIR, 'model_iter_400')
model = SSD300(n_fg_class=2, pretrained_model=pretrained_model)


def handler(_itr, ctx):
    for img in _itr:
        img = img.transpose(2, 0, 1)
        try:
            bboxes, labels, scores = model.predict([img])
        except Exception as e:
            print(e)
        res = {'box': bboxes[0], 'objects': labels[0], 'scores': scores[0]}
        cnt = float(np.sum(res['objects'] == 0))
        result = {
            'num': cnt,
            'score': res['scores'],
            'box': res['box']
        }
        yield result

モデルバージョンの作成

今回学習したモデルは、前回の記事で作成したものとモデル構造が異なるだけで目的は同じです。 そのため、前回作成たモデルの別バージョンとしてモデルを追加します。こうすることで、エンドポイントの切り替えをシームレスに実行できるからです。

ABEJA Platformの管理コンソールから「Model」を選択し、「CreateVersion」をします。このとき、ABEJA Platform上でモデルを学習しているので、ソースコードとしてモデルをimportするのではなく、「Training Artifact」から学習したモデルを選択します。

図10.WebAPIとして使用するモデルのバージョンの追加

デプロイ

モデルバージョンが追加できれば、前回の記事と同じ手順でWebAPIを作成することができます。「Deployment」メニューから、「Create HTTP Service」ボタンを押下します。Versionに今回学習したモデルのバージョンを選択すれば、学習したモデルを使ったWebAPIが作成できます。

図11. HTTPサービスの作成

ちゃんと動くかチェックしましょう。

図12. ABEJA Platform上でのAPIのテスト

無事にWebAPIとして動いてそうですね。

エンドポイントの切り替え

先の記事で、エンドポイントを切り替えることで、モデルのバージョンをシームレスに切り替えることができると紹介しました。同様の手順で、SSDバージョンにモデルを切り替えてみましょう。

前回記事ですでにエンドポイントエイリアスの設定は済んでいますので、「Primary」ボタンを選択してバージョンを選択し直すだけです。 これだけでChocoballDetectorに反映されているはずです!

図13.エンドポイントの切り替え
成果物

成果物

ということで、SSDモデルに切り替えたChocoballDetectorが完成しました。Faster R-CNNのバージョンとSSDのバージョンで速度に違いが出るか確認してみましょう。なお、ChocoballDetectorを試してみたい方はこちらから試せます。

モデルが切り替わっているということは確認できますね。

それぞれ1回ずつの結果なので、厳密な速度比較では無いですが、SSDの方が速そうです。 今までは、画像をpostしてしばらく待っていたのがだいぶ改善された気がします。 しかし、パッケージの認識がなかなかされなくて、精度面ではFasterR-CNNの方が良さそうです(パッケージの認識は必要じゃないんですけどね)。

まとめ

以上、本記事では、ABEJA Platformを利用して、機械学習モデルを学習し、WebAPIとして公開する手順を紹介しました。

前回の記事でも記載しましたが、ABEJA Platformはあくまでプラットフォームであり、実装の制限があまりなく自由に扱えるのは良いですね。 また、ABEJA Platformの特長であるモデルのバージョン管理については、 利用するのは非常に簡単で、モデル、ソースコード、データセット、ハイパーパラメータをセットで管理できるので実験の管理がしやすいと思います。このあたりは、機械学習用プラットフォームと謳っているだけのことはあるなと思います。

一方、2.2節で記載していますが、交差検証やvalidationデータでの評価等が実際の実験では必要になってきます。しかし、それらをサポートする機能は今の所無いように思えます。jupyter notebookが使えたり、学習済みモデルファイルをダウンロードできたりするので、 自分で書いていけばなんでもできはしますが、そこは少しハードルがあるかなと感じました。

終わりに

ABEJA Platformを利用してみたいと思われた方、 問い合わせフォームが用意されていますのでご連絡していただけたらと思います。 また、本記事と合わせて質問等ありましたら弊社でも受けられますので、 お気軽にご連絡いただけたらと思います。

弊社では、様々な機械学習手法を扱いますが、最終的に「活用」ができなければ意味がないと考えています。 業務に合わせたモデル選択と業務アプリケーションとしての実装までをお手伝いさせていただきます。 お気軽にご連絡ください。

また、チョコボールが気になった方は、弊社社員の個人ブログですが、こちらも合わせてご参照いただけたらと思います。

チョコボール統計 – http://chocolate-ball.hatenablog.com

参考文献

[1] Shaoqing Ren, Kaiming He, Ross Girshick, Jian Sun. “Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks”, NIPS (2015).

[2] Liu, Wei, et al. “SSD: Single Shot MultiBox Detector.” arXiv preprint arXiv:1512.02325 (2015).

脚注

1 このブログを読んでいらっしゃるということは、何らかの形で機械学習の取り組みに触れている方だと想像します。
2 最近の機械学習アルゴリズムの主流は、モデル構造が複雑(非線形)で、 解析的にパラメータを決めることが困難(あるいは不可能)なケースが多いです。 そのため、探索的なアプローチがよく用いられます。
3 バージョンの不整合でライブラリが動かないなんて経験みなさんもありますよね?
4 問題を見つけて何度も繰り返すということは機械学習プロジェクトに於いて非常に重要です
5 管理が雑だとどんなデータ、パラメータで学習したモデルがわからなくなり、効率的に実験を進められないですよね
6 私のことなんですけどね
7 学習済みモデルが公開はされていますが、公開されている学習済みモデルには「チョコボール」なんてクラス存在しないので、学習させる必要があります
8 概念がわかってしまえば、難しいことは特に無かったです
9 この記事にあるように有料版のPyCharmを使えばデバッグはできるみたいです

この投稿へのコメント

コメントはありません。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

この投稿へのトラックバック

トラックバックはありません。

トラックバック URL