のんびりしているエンジニアの日記

ソフトウェアなどのエンジニア的な何かを書きます。

Pythonの環境を管理・再現する

皆さんこんにちは
お元気ですか。3月末というのに、雪積もっていてすごかったです。

さて、本日はPythonでの環境の管理方法を紹介します。
今まではAnacondaを利用しており、それを利用してimport/exportする方法もあります。
これに加えて最近はPipenvも増えたようでもあり、それら紹介をします。

なぜ、環境の管理をする必要があるのか?

誰もが同じ環境を再現し、環境による問題を起こさないためです。
仕事やOSSで複数人でのプロジェクトで環境を統一しないと、依存ライブラリのバージョンによる問題が発生することもあります。
そのため、環境を統一することは非常に重要です。

環境構築の方法

本章では、大きく分けて3つの方法を紹介します。

1. requirement.txt
2. Anacondaの仮想環境
3. Pipenv

requirement.txt

古来からある方式であるrequirement.txtです。
未だにライブラリでも多く採用されている方式の一つです。
requeirement.txtはライブラリの名前が一覧で書いており、それを読み込むことで必要なライブラリをインストールできます。

requirement.txtの作り方は次の通りです。

pip freeze > requirement.txt

読み込み方は次の通りです。

pip install -r requirement.txt

この方法ですが、pipコマンドが実行できれば、実行可能なのがメリットです。
AnacondaやPipenvでもpipが利用できるので使えます。
ただ、Pythonのバージョンは管理できていないので、そこは一つネックになるポイントです。

Anacondaの仮想環境

Anacondaを用いて、仮想環境を作成できます。
基本的な利用方法は次の通りです。
nonbiri-tereka.hatenablog.com

Anacondaによる仮想環境の構築は次のコマンドで作成可能です。

conda create -n py38 python=3.8

その仮想環境の情報を出力するには次のコマンドを実行します。

conda env export > condatest.yml

出力されたファイルを読み込む方法は次の通りです。

conda env create -n py38_re -f condatest.yml

仮想環境ごと環境構築ができるのでPythonのバージョンも管理できるのが強みです。
ただし、Anacondaそのものをインストールせねばならず、ディスクの容量も必要とする点がネックでしょうか。

Pipenv

Pipenvが最近は利用されるようになっています。
まずは、pipenvをインストールします。

pip install pipenv

次にpipenvを利用した環境構築を行います。

pipenv --python 3.7 # python3.7での環境構築
pipenv install numpy scipy matplotlib

逆にPipenvの環境を取り込み、構築する場合は次の通りです。
Pipfileがあるディレクトリで、次のコマンドを入力します。

pipenv install 

pipenvは導入までの時間が非常に早く、手軽に構築できるのが良い点だと感じました。

正直どれが一番なのか

どれも一長一短ですが、Pipenvが楽そうです。
Anacondaだとそのインストール自体も必要になり、導入コストが非常に高いと感じています。
また、requirement.txtのみだと、同一PC内での複数環境での管理が難しくなるので、避けたほうが良いと思っています。

最後に

これらそれぞれに利点欠点もあるため、ケースバイケースで考慮すべきでしょう。
このあたりは使いながら検討する必要があると感じています。

PyTorchで高精度・高性能のEfficientNetを利用する

皆さんこんにちは
お元気でしょうか。

本日はEfficientNetをPyTorchで利用します。
私も頻繁に利用しますが、時々忘れてしまうのでメモ

EfficnetNetについて

EfficientNetとは?

幅、深さや解像度に関係性を導き出されたニューラルネットワークアーキテクチャ
SoTAを出したが、従来より8.4倍小さく、6.1倍の高速化を実現しています。
EfficientB0-B7(今はそれ以上もあった気もしますが)まで存在し、精度・性能により使い分けできます。
Kagglerたちは最近このニューラルネットワークをソリューションに利用するようになりました。(ResNetより増えている気がする)
arxiv.org

ライブラリのインストール

使うライブラリのインストールは簡単です。

pip install efficientnet-pytorch

github.com

EfficientNet-Pytorchの使い方

モデルの初期化方法

モデルの初期化方法は学習済モデルの有無により、変わります。
学習済モデルがないバージョン

model = EfficientNet.from_name("efficientnet-b5")

学習済モデルを利用するバージョン

model = EfficientNet. from_pretrained("efficientnet-b5")

Fine-turning

ImageNetと同じクラスの問題を解く要望はほぼ皆無だと思います。
学習済のモデルを元にfineturningを行いましょう。
基本的には最終層を変更するのみなので、私は次のようにモデルを構築して学習を行っています。

n_class = 100
model = EfficientNet. from_pretrained("efficientnet-b5")
model._fc = nn.Linear(2048, n_class)

後は通常通りモデルを学習すれば良いです。

最後に

EfficientNetは精度も高く性能もそれなりに良いです。
PNASNetなどよりも圧倒的に利用しやすい感じがしているのでResNetなどとのアンサンブルに有効です。

MLFlow Trackingを使って、実験管理を効率化する

皆さんこんにちは
お元気でしょうか。COVIT-19起因で引きこもっているため、少しずつ自炊スキルが伸びていっています。

以前、実験管理に関していくつかのソフトウェアを紹介しました。
その中で、MLFlow Trackingが一番良さそうではあったのでパイプラインに取り込むことを考えています。
もう少し深ぼって利用方法を把握する必要があったので、メモ代わりに残しています。

nonbiri-tereka.hatenablog.com

MLFlow Trackingのおさらい

MLFlowとは

MLFlowはプラットフォームです。機械学習のデプロイやトラッキング、実装のパッケージングやデプロイなど幅広くサポートしています。
その中ではいくつかの機能があり、主にMLflow Trackingを実験管理に利用している人が増えています。
Trackingの機能については申し分がなさそうで、リモートサーバでの運用なども対応できるので、複数人で利用するにも有用でしょう。(大体以前と同じ)

github.com

基本的な利用方法

実装

実装はほぼ以前と同様ですが、以下の通りです。

import argparse
import os
import tempfile

import mlflow
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
from torchvision import datasets, transforms

# Command-line arguments
parser = argparse.ArgumentParser(description='PyTorch MNIST Example')
parser.add_argument('--batch-size', type=int, default=64, metavar='N',
                    help='input batch size for training (default: 64)')
parser.add_argument('--test-batch-size', type=int, default=1000, metavar='N',
                    help='input batch size for testing (default: 1000)')
parser.add_argument('--epochs', type=int, default=10, metavar='N',
                    help='number of epochs to train (default: 10)')
parser.add_argument('--lr', type=float, default=0.01, metavar='LR',
                    help='learning rate (default: 0.01)')
parser.add_argument('--momentum', type=float, default=0.5, metavar='M',
                    help='SGD momentum (default: 0.5)')
parser.add_argument('--enable-cuda', type=str, choices=['True', 'False'], default='True',
                    help='enables or disables CUDA training')
parser.add_argument('--seed', type=int, default=1, metavar='S',
                    help='random seed (default: 1)')
parser.add_argument('--log-interval', type=int, default=100, metavar='N',
                    help='how many batches to wait before logging training status')
args = parser.parse_args()

enable_cuda_flag = True if args.enable_cuda == 'True' else False

args.cuda = enable_cuda_flag and torch.cuda.is_available()

torch.manual_seed(args.seed)
if args.cuda:
    torch.cuda.manual_seed(args.seed)

kwargs = {'num_workers': 1, 'pin_memory': True} if args.cuda else {}
train_loader = torch.utils.data.DataLoader(
    datasets.MNIST('../data', train=True, download=True,
                   transform=transforms.Compose([
                       transforms.ToTensor(),
                       transforms.Normalize((0.1307,), (0.3081,))
                   ])),
    batch_size=args.batch_size, shuffle=True, **kwargs)
test_loader = torch.utils.data.DataLoader(
    datasets.MNIST('../data', train=False, transform=transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.1307,), (0.3081,))
    ])),
    batch_size=args.test_batch_size, shuffle=True, **kwargs)


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
        self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
        self.conv2_drop = nn.Dropout2d()
        self.fc1 = nn.Linear(320, 50)
        self.fc2 = nn.Linear(50, 10)

    def forward(self, x):
        x = F.relu(F.max_pool2d(self.conv1(x), 2))
        x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
        x = x.view(-1, 320)
        x = F.relu(self.fc1(x))
        x = F.dropout(x, training=self.training)
        x = self.fc2(x)
        return F.log_softmax(x, dim=0)


model = Net()
if args.cuda:
    model.cuda()

optimizer = optim.SGD(model.parameters(), lr=args.lr, momentum=args.momentum)


def train(epoch):
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        if args.cuda:
            data, target = data.cuda(), target.cuda()
        data, target = Variable(data), Variable(target)
        optimizer.zero_grad()
        output = model(data)
        loss = F.nll_loss(output, target)
        loss.backward()
        optimizer.step()
        if batch_idx % args.log_interval == 0:
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, batch_idx * len(data), len(train_loader.dataset),
                       100. * batch_idx / len(train_loader), loss.data.item()))
            step = epoch * len(train_loader) + batch_idx
            log_scalar('train_loss', loss.data.item(), step)


def test(epoch):
    model.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            if args.cuda:
                data, target = data.cuda(), target.cuda()
            data, target = Variable(data), Variable(target)
            output = model(data)
            test_loss += F.nll_loss(output, target, reduction='sum').data.item()  # sum up batch loss
            pred = output.data.max(1)[1]  # get the index of the max log-probability
            correct += pred.eq(target.data).cpu().sum().item()

    test_loss /= len(test_loader.dataset)
    test_accuracy = 100.0 * correct / len(test_loader.dataset)
    print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        test_loss, correct, len(test_loader.dataset), test_accuracy))
    step = (epoch + 1) * len(train_loader)
    log_scalar('test_loss', test_loss, step)
    log_scalar('test_accuracy', test_accuracy, step)


def log_scalar(name, value, step):
    """Log a scalar value to both MLflow and TensorBoard"""
    mlflow.log_metric(name, value)

active_run = mlflow.active_run()
for key, value in vars(args).items():
    mlflow.log_param(key, value)
# Create a SummaryWriter to write TensorBoard events locally
output_dir = dirpath = tempfile.mkdtemp()
# Perform the training
for epoch in range(1, args.epochs + 1):
    train(epoch)
    test(epoch)
# Upload the TensorBoard event logs as a run artifact
with tempfile.TemporaryDirectory()  as tmp:
    filename = os.path.join(tmp, "model.bin")
    torch.save(model.state_dict(), filename)
    mlflow.log_artifacts(tmp, artifact_path="results")

既存実装からそこまで改修を加える必要はありません。
mlflowの開始前には、active_run()を行います。
その後、mlflowの関数利用して、記録を残せます。記録を残すための関数は次の通りです。

項目 説明
log_param パラメータを記録する。batch_sizeなど
log_metric 時間で経過する数値を記録する。train_lossなど
log_artifacts 出力されたファイルを記録する。モデルなど

可視化

一覧画面

分析の一覧画面は次の通りです。
f:id:tereka:20200321195334p:plain

Searchの箇所にクエリを入力し、データを絞ることが可能です。
また、「Columns」の左にある表示のクリックによりテーブルのUIを変更できます。
更には各メトリクスの値をクリックすることでソートが可能です。

個別画面

一般的な実験条件です。こちらは特に目新しいこともなく、情報が記載されています。
f:id:tereka:20200321200048p:plain

各ステップごとのメトリクスや実験で保存したファイルをmlflowの画面から閲覧できます。
f:id:tereka:20200321200057p:plain

メトリクス

取得したメトリクスは次のようにグラフとして可視化できます。
Pointを点にしたり、対数でスケーリングしたりすることも可能です。
f:id:tereka:20200321200302p:plain

比較画面

一覧の画面から複数の実験を選択し、「Compare」を押すと次のような画面に進みます。
この画面で実験の比較を行えます。複数の実験が一覧で並んでいるため、左右見比べたりするのは容易です。

f:id:tereka:20200321214411p:plain

より使いやすくするために

これまで標準的な方式を紹介しましたが、一歩使いやすくするためのTipsを以下に紹介します。

リモートサーバへの送信

今回はリモートのサーバを利用することにします。
もともとローカルに出力されていますが、set_tracking_uriを実行することで、ファイルの配置先、リモートへの出力も可能とします。
まず、リモートにmlflowサーバの立ち上げを行います。

mlflow server -h 0.0.0.0 -p 5000

次にリモートサーバへの設定を行います。
active_run関数の前にset_tracking_url関数を実行します。
サーバのエンドポイントはログを見る限り、httpsで起動していると思われがちですが、httpで記述する必要があります。
(ここですが、httpsで記述していて、少しはまりました)

この状態でもUIは利用できるので、記録が成功しているかはアクセスすればわかります。

mlflow.set_tracking_uri("http://192.168.1.5:5000")

また、実装を変更したくない場合、MLFLOW_TRACKING_URI環境変数の設定による変更も可能です。

実験名の設定

通常だと、名前がなく、日付のみで管理し難いです。
mlflowは、<実験>/の階層で管理しています。
これらの指定により管理しやすくなります。例えば、実験とrun_idを指定するには次のコードを記載すれば良いでしょう。

eid = mlflow.create_experiment("experiment")
mlflow.start_run(experiment_id=eid, run_name="run01")

既に実験を作成した場合は、get_experiment_by_name関数を呼び出すとexperiment_idを取得できます。

セキュリティ

セキュリティはmlflow側で担保していないので、別のソフトウェア(例:nginx)を利用する必要があります。
例えば、dockerを利用する場合だと次のサイトが参考になります。
ymym3412.hatenablog.com

最後に

MLFlow Trackingの利用をすれば予想通り、リモートで集約するなども可能です。
自分の作りたいものが作れそうではあるので、これを使いこなしてより実験管理を効率化したいと感じています。

Pythonで自然言語処理のタスクをやってみる。

皆さんこんにちは
お元気ですか。アドベントカレンダー真っ盛りですね。
本日は「python Advent Calendar 2017」のアドベントカレンダー第5日です。

qiita.com

自然言語処理には様々なライブラリ(NLTKやCoreNLP)があります。
せっかくの機会として、本記事では紹介が少ないspaCyを紹介します。

spaCy

spaCyとは

spaCyはPythonの発展的な自然言語処理のライブラリです。
実際に使われていることを想定しており、英語、ドイツ語、フランス語、スペイン語に対応しています。
トークナイザーは日本語もあるとのこと(確かJanomeで動作します)。

github.com

次のリンク先には他の自然言語処理ライブラリの
アルゴリズムの観点や精度(Dependency parsing、Named entity comparison)が
載っており、他のライブラリとどのような点が違うのかを確認できます。

spacy.io

インストール

pip install spacy

spaCyで遊んでみる

spaCyですが、日本語対応が用意されています。
ただ、残念ながら日本語に対応したモデルがないため、
まだ本格的な日本語解析は難しいのかなと感じています。

それは置いておいて、本日はspaCyの機能紹介をしたいので、
既に準備されている英語のモデルを使います。

まずは、英語解析の準備をします。

import spacy
nlp = spacy.load('en')
doc1 = nlp(u"this is a pen.")

文章を単語ごとに区切る

文章を単語ごとに区切ることが可能です。

for token in doc1:
    print (token)

結果はスペースで区切られている次の通りです。

this
is
a
pen
.

文章を文ごとに分ける

簡単そうな例ですが、文章を文ごとに分解します。
英語だと、ピリオドによる分割になるでしょうか。

doc2 = nlp(u"This is a pen. That is an apple")
for sent in doc2.sents:
    print (sent)

出力結果です。「.」による分割ができています。

This is a pen.
That is an apple

固有表現抽出(Named Entity Recognizer)

固有表現抽出は文章の中から固有の表現を発見する技術です。

ent_doc = nlp("Rami Eid is studying at Stony Brook University in New York")
for ent in ent_doc.ents:
    print (ent, ent.label, ent.label_)

残念ながらNew Yorkが出力できていません。
固有表現が何かだけではなく、どんな固有表現(人なのか、組織なのか)かも抽出できています。

Rami Eid 377 PERSON
Stony Brook University 380 ORG

名詞フレーズの抽出

フレーズの抽出も可能です。

noun_chunk_test = nlp(u"Natural language processing (NLP) deals with the application of computational models to text or speech data.")
for noun_chunk in noun_chunk_test.noun_chunks:
    print (noun_chunk)

出力結果です。これもそれっぽいですね。

Natural language processing (NLP) deals
the application
computational models
text or speech data

文章間の距離を計算する。

文書のベクトルを計算します。
word2vecに類似したアルゴリズムです。実装は非常にシンプルで
apple.vectorをすれば、文書のベクトルを取得できます。
ベクトルを表示すると標準出力が長くなるので、代わりにshapeを実行してみます。

apple.vector.shape

また、2ベクトル間を計算し、類似度を算出できます。

udon = nlp(u"Udon and oranges are not similar.")
apple = nlp(u"Apples and oranges are similar. Boots and hippos aren't.")
udon.similarity(apple)

最後に

今回は自然言語処理のライブラリを紹介しました。
まだ、日本人向けには難しい状態でありますが、開発が進んでいるので、試しに使ってみてはいかがでしょうか。

CatBoostを5分程度で動かしてみた

皆さんこんにちは
お元気ですか?私は年末の師走フラグが立っています。

少し前(この界隈ではだいぶ前っぽい)にYandex社からCatBoostが発表されました。
これが発表されたことは知っていたのですが、時間が取れなくて利用してなかったソフトウェアの1つです。

CatBoost

CatBoostはYandex社が開発した勾配ブースティングをベースとした機械学習のライブラリです。
catboost.yandex

公式サイトには次の特徴が記載されています。

  1. 過学習を減らす。・・・独自のアルゴリズムによって実現した。
  2. カテゴリ特徴量の対応・・・前処理を行わなくてもカテゴリ特徴量に対応をしている。
  3. ユーザフレンドリなAPI・・・コマンドラインツールもしくは、Python,RのAPIをサポートしている。

公式ページ記載のベンチマークは他のLight GBMやxgboostよりも
よく見え、かつ新しい標準的な手法であることを主張しているのでぜひ使ってみたい所です。

※ICML2017で、少し資料があがっているようです。
https://indico.cern.ch/event/617754/contributions/2590694/attachments/1459648/2254154/catboost_for_CMS.pdf

実際に使ってみる。

インストール

まずは、インストールですが簡単にできます。
(あれ・・難しくなかった・・・)

pip install catboost

実行する

まずは、インストールできていることを確認しましょう。
試しにインストールしたライブラリをインポートしてみます。

from catboost import CatBoostRegressor

エラーが出ず、実行が完了すればインストールは成功しているはずです。

簡単な実行サンプルの紹介

CatBoostには大きく分けて2つのクラスがあります。

1つ目がCatBoostRegressorで回帰問題を解くクラス、
2つ目はCatBoostClassifierで分類問題を解くクラスになります。

解く問題によって、必要なクラスを選択することになるでしょう。

回帰問題

import numpy
from catboost import CatBoostRegressor
# 学習データ
dataset = numpy.array([[1,4,5,6],[4,5,6,7],[30,40,50,60],[20,15,85,60]])
train_labels = [1.2,3.4,9.5,24.5]
model = CatBoostRegressor(learning_rate=1, depth=6, loss_function='RMSE')
fit_model = model.fit(dataset, train_labels)

model.predict(dataset)

分類問題

from catboost import CatBoostClassifier
cat_features = [0,1,2]
train_data = [["a","b",1,4,5,6],["a","b",4,5,6,7],["c","d",30,40,50,60]]
train_labels = [1,1,-1]
test_data = [["a","b",2,4,6,8],["a","d",1,4,50,60]]
# CatBoostClassifierの初期化
model = CatBoostClassifier(iterations=2, learning_rate=1, depth=2, loss_function='Logloss')
# モデルの学習
model.fit(train_data, train_labels, cat_features)
# クラス予測
preds_class = model.predict(test_data)
# 各クラスの確率を予測
preds_proba = model.predict_proba(test_data)
# 変換していない出力数値
preds_raw = model.predict(test_data, prediction_type='RawFormulaVal')

比較的どちらもscikit-learnのインターフェースに酷似しています。
ただ、他のライブラリと異なる特徴的な部分があります。
それは、fit関数のインターフェースのcat_features変数です。

cat_featuresはカテゴリカル変数のインデックスのリストになります。
この指定をすると、カテゴリカル変数的な処理が実行されます。

見た方が良さそうなページ

最後にその他、見た方が良さそうなページを紹介します。

Usage examples

Binary Classification, Multiclassification, Regressionの簡単なサンプルや
その他実装上で利用できるコードが掲載されています。

基本的な実装はこのページを確認すれば、参考にできると思います。

tech.yandex.com

Training parameters

無数にある学習パラメータの一覧が掲載されています。
最初軽く説明しようと思ったら案外、膨大でした。
これを見ながら後に勉強しようと思います。

tech.yandex.com

最後に

今回、CatBoostを試しに動かしてみました。
実際に動かしたばかりでどの程度の精度が出るか、使いやすいかまでは至れていませんが
コンペで試運転をはじめてみようと思います。