20191213のPythonに関する記事は30件です。

価値反復法のeinsumによる実装

普段強化学習,逆強化学習を研究しているのですが,最近おもしろいことを知って実装してみたので,アドベントカレンダーのネタにさせていただきます.

予備知識

前提となるマルコフ決定過程と価値反復法について概説します.

マルコフ決定過程

マルコフ決定過程(MDP)とは,マルコフ過程(次状態は現状態にのみ影響されるマルコフ性をもつ過程)に意思決定である行動を加えた過程です.詳しい説明はネット上に多くあると思うので割愛します.

価値反復法

価値反復法(Value Iteration)とは,動的計画法と呼ばれるアルゴリズムの一種です.
ある状態$s$の価値を次の手順で求めます.

  • $V$, $Q$ を初期化
    • $s$, $a$ について収束するまで繰り返し:
    • $Q(s,a)=R(s)+\gamma \sum_{s^{\prime}} P(s^{\prime}|s,a)V(s^{\prime})$を計算
    • $V(s)=\max_a Q(s,a)$

einsumによる実装

numpy.einsumは,アインシュタインの縮約記法による行列計算の実装を手軽に行なえます.
今回の場合,上の式の右辺の$\sum_{s^{\prime}} P(s^{\prime}|s,a)V(s^{\prime})$の項は次のように書けます.

np.einsum("ijk,k->ij", P, V)

コード

Gridworld(格子世界)という強化学習の基本中の基本の問題で実験しました.
einsumによる実装と,よくあるループによる実装の計算時間が,Gridの一辺の長さを増やしていった際にどう変化するのか比較しました.

vi.py
# -*- coding: utf-8 -*-

import numpy as np
from makeP import makeProb
import time

for statenum in range(2, 101):

    list_einsum = []
    list_loop = []

    for ite in range(100):
        print(".", end="", flush=True)
        x_size = statenum
        y_size = statenum

        n_states = int(x_size * y_size)

        P = makeProb(x_size, y_size)

        R = np.ones(n_states)*-0.01
        R[int(n_states-1)] = 1

        Q = np.zeros((n_states, 4))
        V = np.zeros(n_states)

        delta = np.inf
        eps = 0.0

        gamma = 0.95

        t1 = time.time()
        for _ in range(10000):
            Q = R[:, None] + gamma * np.einsum("ijk,k->ij", P, V)
            V = np.max(Q, axis=1)
        t2 = time.time()
        list_einsum.append(t2-t1)

        Q = np.zeros((n_states, 4))
        V = np.zeros(n_states)

        t1 = time.time()
        for _ in range(10000):
            for s in range(n_states):
                for a in range(4):
                    Q[s][a] = R[s, None] + gamma * np.dot(P[s,a,:], V)
                V[s] = np.max(Q[s, :])
        t2 = time.time()
        list_loop.append(t2-t1)
    print("")
    ar_einsum = np.array(list_einsum)
    ar_loop = np.array(list_loop)

    print("EINSUM: ", "size: ", statenum, "mean: ", np.mean(ar_einsum), "median: ", np.median(ar_einsum), "stdev: ", np.std(ar_einsum))
    print("LOOP  : ", "size: ", statenum, "mean: ", np.mean(ar_loop),   "median: ", np.median(ar_loop),   "stdev: ", np.std(ar_loop))

makeP.py
# -*- coding: utf-8 -*-

import numpy as np

def makeProb(x_size, y_size, n_action=4):
    # make transition prob

    n_states = x_size*y_size
    # S, A, S'
    P = np.zeros(( n_states, n_action, n_states ))



    for s in range(n_states):

        #left is wall
        if s%x_size == 0:
            if s == 0:
                P[0][1][5] = 1
                P[0][3][1] = 1
            elif s > (x_size*(y_size-1)-1):
                P[s][0][s-x_size] = 1
                P[s][3][s+1] = 1
            else:
                P[s][0][s-x_size] = 1
                P[s][1][s+x_size] = 1
                P[s][3][s+1] = 1

        #right is wall
        elif (s+1)%x_size == 0:
            if s == (x_size*y_size-1):
                P[s][0][s-x_size] = 1
                P[s][2][s-1] = 1
            elif s < x_size:
                P[s][1][s+x_size] = 1
                P[s][2][s-1]=1
            else:
                P[s][0][s-x_size] = 1
                P[s][1][s+x_size] = 1
                P[s][2][s-1] = 1

        #upper is wall
        elif s < x_size and s%x_size!=0 and (s+1)%x_size !=0 :
            P[s][1][s+x_size] = 1
            P[s][2][s-1] = 1
            P[s][3][s+1] = 1

        #lower is wall
        elif s > (x_size*(y_size-1)-1) and s%x_size!=0 and (s+1)%x_size !=0:
            P[s][0][s-x_size] = 1
            P[s][2][s-1] = 1
            P[s][3][s+1] = 1

        else:
            P[s][0][s-x_size] = 1
            P[s][1][s+x_size] = 1
            P[s][2][s-1] = 1
            P[s][3][s+1] = 1

    return P

結果

einsumの圧勝.

computing time.png

何が嬉しいのかというと,例えば逆強化学習のInner-loop(内部にある強化学習のループ)が速く解けるようになったり.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoでよく見かけるエラー =随時追加=

環境

python3,7
Django 2.2
windows

フォルダー構成
ここで触れないファイルは省略します。

├── config
│   ├──__init__.py
    ├──settings.py
│   └── urls.py
├── manage.py
├── 
├── 
└── posts
    ├──templates ── posts ── index.html
    ├── models.py
    ├──forms.py
    ├──views.py
    └── urls.py

項目

1.TemplateDoesNotExist
2.

内容

1.TemplateDoesNotExist

原因

テンプレートが見つからない

確認すること

htmlを置く場所が間違っていないか
urls.pyのpathが間違っていないか。

#views.py
class IndexView(TemplateView):
    template_name='posts/index.html'
posts_index=IndexView.as_view()

#urls.py
app_name="posts"
urlpatterns=[
    path('',views.posts_index,name='posts_index')
]

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonとは

今日はある意味本命である
Pythonについて投稿します。

Pythonとは

1991年にオランダ人のグイド・ヴァン・ロッサム氏によって開発されたプログラミング言語です。

少ないコードで簡単にプログラムを作れ
尚且つコードが読みやすいのが特徴です。

「人工知能(AI)」や「Web開発」、「教育の分野」など活用が広まっています。

Pythonのメリット

基本メリットとして

  • シンプルで覚えやすい
  • インデントによるオフサイドルール
  • コンパイルが不要
  • ライブラリが充実している

これらが挙げられます。

シンプルで覚えやすい

文法がシンプルで必要最低限のものしかあらず
読みやすく書きやすい言語です。
何通りも書き方は用意されてないので
他人の書いたコードも比較的読みやすいものになります。

インデントによるオフサイドルール

構文などのブロックを字下げ(インデント)で指定するというものです。
これによって誰が書いても同じコードになるため、
プログラムは書きやすく読みやすいモノになっています。

オフサイドルール

字下げによって文などのかたまりの範囲(ブロック)を示す規則です。

コンパイルが不要

コンパイル作業は、大量のエラーが発生することが多く時間を要します。
Pythonはインタプリタ型の言語のため、コンパイルの必要がなく
すぐに動作確認ができます。

Pythonのデメリット

デメリットは以下の通りです。

  • 実行速度が遅い
  • インデントが必須

Pythonはインタプリタ言語のなかでも特に実行速度が遅い言語と言われています。
企業向けの基幹システム,システムや高度なゲーム開発には不向きです。

またオフサイドルールからインデントが適切にできていないと
エラーとなってしまうこともあります。

Pythonで作れるもの

作れる物は

  • 「Webアプリケーション」
  • 「デスクトップアプリケーション」
  • 「組み込みアプリケーション」
  • 「ゲーム」
  • 「機械学習(人工知能)」

などが作れます。

Pythonの作成例

有名なのをあげると

  • youtube
  • evernote
  • instagram

が代表的です。

Pythonのフレームワーク

使われいるフレームワークを一部紹介するとこのような物があります。

  • Django
  • Flask
  • Bottle
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ダウンロード画像と顔画像の簡易管理アプリ

はじめに

  • 機械学習用の画像を収集し、目視で仕分けする作業が発生する場合があります。
  • Mac では、Finder で画像を見ながら削除を繰り返したりしますね。これは、かなり骨の折れる作業です。
  • 今回は、Webブラウザベースの簡易管理アプリを作成しました。
  • ソース一式は ここ です。

概要

  • トップページでは、config.pyCLASSES を元に各メニューを表示しています。
  • ダウンロード画像、顔画像では、両者を比較することができます。
  • また、顔画像をクリックし、まとめて削除を実施できます。
  • 学習画像の予測結果テスト画像の予測結果は、別途提供する予定です。

トップページ

image.png

ダウンロード画像と顔画像

image.png

ライブラリ

  • Flask を用いています。
  • Pillow は、画像の拡大縮小に用いています。
  • Bootstrap を用いています。
  • Font Awesome で特殊なアイコンを表示しています。今回は、顔画像の右上に表示するゴミ箱のアイコンで利用しています。

トップページ

  • トップページは、index.html の内容をほぼそのまま表示しています。
  • config.pyCLASSES は、index.htmlitems として渡しています。
  • 各メニューの識別のために、Bootstrap で色を変えています。
image_viewer.py
@app.route('/')
def index():
    """Top Page."""
    return render_template('index.html', items=CLASSES)
templates/index.html
    <div class="container-fluid">
      {% for item in items %}
      <div class="row">
        <div class="col">
          {{ loop.index }} {{ item }}
        </div>
        <div class="col-11">
          <a class="btn btn-primary" href="/download_and_face/{{ item }}" role="button">ダウンロード画像、顔画像</a>
          <a class="btn btn-secondary" href="/predict/train/{{ item }}" role="button">学習画像の予測結果</a>
          <a class="btn btn-success" href="/predict/test/{{ item }}" role="button">テスト画像の予測結果</a>
        </div>
      </div>
      <br />
      {% endfor %}
    </div>

ダウンロード画像と顔画像

  • DOWNLOAD_PATH 配下には、Google カスタム検索等でダウンロードした画像が保存されています。
  • 例: data/download/安倍乙/0001.jpeg
  • FACE_PATH 配下には、OpenCVHaar Cascade を用いて顔認識した画像が保存されています。
  • また、ダウンロード画像のファイル名を元にファイル名を生成しています。
  • 例: data/face/安倍乙/0001-0001.jpeg

GET の処理

  • 安倍乙 などを item で受け取ります。
  • DOWNLOAD_PATH item *.jpeg をキーとして、ダウンロード画像の一覧を作成します。
  • FACE_PATH item *.jpeg をキーとして、顔画像の一覧を作成します。
image_viewer.py
@app.route('/download_and_face/<item>', methods=['GET', 'POST'])
def download_and_face(item):
    """ダウンロード画像、顔画像."""

    download_list = glob.glob(os.path.join(DOWNLOAD_PATH, item, '*.jpeg'))
    download_list = sorted([os.path.basename(filename) for filename in download_list])
    face_list = glob.glob(os.path.join(FACE_PATH, item, '*.jpeg'))
    face_list = sorted([os.path.basename(filename) for filename in face_list])
  • ダウンロード画像から顔画像の検索キーを作成し、配列 row を作成しています。
  • 各ダウンロード画像と顔画像の組み合わせは、rows に再格納されます。
  • itemrows をテンプレートエンジンへ引き渡します。
image_viewer.py
    rows = []
    for download in download_list:
        row = [download]
        key = download.split('.')[0] + '-'
        for face in face_list:
            if face.startswith(key):
                row.append(face)
        rows.append(row)

    return render_template('download_and_face.html', item=item, rows=rows)
  • テンプレートでは、ダウンロード画像と顔画像の組みを 1行 で表示します。
  • ダウンロード画像と顔画像のリンクを作成します。
  • size=200 で画像のサイズを指定しています。このサイズは、画像の縦のサイズとなります。
  • サイズを揃える事で、ダウンロード画像と顔画像の比較がしやすくなります。
  • 顔画像では、クリックで指定しやすい様に CSSJS を利用しています。
  • こちらは、下記を参考にしました。
templates/download_and_face.html
          <tbody>
            {% for row in rows %}
            <tr>
              <td>
                {{ loop.index }}
              </td>
              <td>
                <figure class="figure">
                  <img src="/data/download/{{ item }}/{{ row[0] }}?size=200" />
                  <figcaption class="figure-caption">{{ row[0] }}</figcaption>
                </figure>
              </td>
              <td>
                {% for filename in row[1:] %}
                <figure class="figure">
                  <label class="image-checkbox">
                  <img src="/data/face/{{ item }}/{{ filename }}?size=200" />
                  <input type="checkbox" name="filename" value="{{ filename }}" />
                  <i class="fa fa-trash-o d-none"></i>
                  </label>
                  <figcaption class="figure-caption">{{ filename }}</figcaption>
                </figure>
                {% endfor %}
              </td>
            </tr>
            {% endfor %}
          </tbody>
  • 顔画像は、チェックボックスを指定した場合、指定しない場合の表示を調整しています。
static/download_and_face.css
.image-checkbox {
  cursor: pointer;
  border: 2px solid transparent;
  box-sizing: border-box;
  -moz-box-sizing: border-box;
  -webkit-box-sizing: border-box;
  position: relative;
}

.image-checkbox input[type="checkbox"] {
  display: none;
}

.image-checkbox-checked {
  border-color: #d9534f;
}

.image-checkbox .fa {
  color: #ffffff;
  background-color: #d9534f;
  font-size: 20px;
  padding: 4px;
  position: absolute;
  right: 0;
  top: 0;
}

.image-checkbox-checked .fa {
  display: block !important;
}
  • 初回表示の時、チェックボックスの状態をクラスに設定しています。
  • また、クリック時にクラスを変更しています。
static/download_and_face.js
// image gallery
// init the state from the input
$(".image-checkbox").each(function () {
  if ($(this).find('input[type="checkbox"]').first().attr("checked")) {
    $(this).addClass('image-checkbox-checked');
  }
  else {
    $(this).removeClass('image-checkbox-checked');
  }
});

// sync the state to the input
$(".image-checkbox").on("click", function (e) {
  $(this).toggleClass('image-checkbox-checked');
  var $checkbox = $(this).find('input[type="checkbox"]');
  $checkbox.prop("checked",!$checkbox.prop("checked"))

  e.preventDefault();
});

画像のサイズの変更

  • ダウンロード画像、顔画像のパスを生成します。
image_viewer.py
@app.route('/data/<folder>/<item>/<filename>')
def get_image(folder, item, filename):
    """画像のレスポンス size で拡大縮小."""

    if folder not in ['download', 'face']:
        abort(404)

    filename = os.path.join(DATA_PATH, folder, item, filename)
  • Pillow を利用して画像を読み込みます。
image_viewer.py
    try:
        image = Image.open(filename)
    except Exception as err:
        pprint.pprint(err)
        abort(404)
  • URL のオプションで size がある場合は、画像のサイズを修正します。
  • 今回の場合は、縦のサイズを元に画像が拡大縮小します。
  • 縦のサイズを、ダウンロード画像と顔画像で揃えると、目視で比較しやすくなりました。
  • また、Pillow には、thumbnail でサイズを変更することもできます。
  • ただ、こちらは縦横の比率が変わってしまいます。
image_viewer.py
    if 'size' in request.args:
        height = int(request.args.get('size'))
        width = int(image.size[0] * height / image.size[1])
        image = image.resize((width, height), Image.LANCZOS)
  • 最後に、Pillow のデータをバイトデータに変換し、image/jpeg でレスポンスを作成します。
image_viewer.py
    data = io.BytesIO()
    image.save(data, 'jpeg', optimize=True, quality=95)
    response = make_response()
    response.data = data.getvalue()
    response.mimetype = 'image/jpeg'

    return response

POST の処理

  • フォームで削除する顔画像のファイル名が POST されます。
  • 対象の顔画像を確認し、os.remove で削除しています。
  • ここは、もう少し慎重な仕組みが必要だと思いますが、個人で利用する事を前提に、簡易な形にしています。
image_viewer.py
@app.route('/download_and_face/<item>', methods=['GET', 'POST'])
def download_and_face(item):
    """ダウンロード画像、顔画像."""

    if request.method == 'POST' and request.form.get('action') == 'delete':
        for filename in request.form.getlist('filename'):
            filename = os.path.join(FACE_PATH, item, filename)
            if os.path.isfile(filename):
                os.remove(filename)
                print('delete face image: {}'.format(filename))

おわりに

  • ダウンロード画像と顔画像を比較しながら、不要な顔画像を削除するWebアプリケーションを作成しました。
  • Mac の Finder を用いて顔画像を削除するよりは、格段に便利になったと思います。
  • Webアプリケーションの作成には、それなりに時間や慣れが必要ですね。
  • 次回は、顔画像を水増しを実施する予定です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoのデプロイで追い詰められているあなたへ。Ubuntu18.04+NginxでDjango2.2デプロイ完全版

はじめに

自分が初心者だったこともあって、かなり初心者向けに書いたつもりです。
とは言っても初投稿なので、1ヶ月ほど前にやったことを思い出しながら書いているので、なにか抜けていたり大きなミスをしていたりするかもしれません。
もしご不明な点やミスなどがあればコメントよろしくおねがいします。

完全版とは言いましたが、よくある記事とは違い、プロジェクトの作成からの説明は行いません。
自分自身Djangoのデプロイに苦労して調べていた時に正直無駄だと思ったからです。
この記事では、デプロイするDjnagoプロジェクトが用意できているものとして説明を始めます。

Django3.0がリリースされた今かよって感じですが、おそらくDjango3.0でもそんなに変わらないと思います。
僕がやった範囲内では問題なく動作しました。

環境

  • Ubuntu18.04 LTS
  • Nginx1.14.0(最新のものなら基本OKだと思う)
  • Python3.8.0(3.x.xならOK)
  • Django2.2.8(2.2.xならOK、おそらく3.0でもいける)

前提

  • プロジェクトの名前は "YourProjectName"
  • アプリの名前は "YourAppName"
  • 好ましくはないが、SECRET_KEYは、開発環境・本番環境ともに、もとのsettings.pyファイルに記載されていたものを使用します。
  • 好ましくはないが、データベースは、開発環境・本番環境ともに、sqlite3を使用します。
  • static、media、templatesフォルダは、トップディレクトリにて一括管理されているものとします。
  • ホスト名(ドメインの名前)はYourHostNameとします。グローバルIPアドレスでも構いません。

ローカルでの準備

準備完了段階でのディレクトリ構造

YourProjekutName
        ├ YourProjectName
        │     ├ __init__.py
        │     ├ settings
        │     │     ├ __init__.py
        │     │     ├ base.py
        │     │     ├ local.py
        │     │     └ production.py
        │     ├ urls.py
        │     └ wsgi.py
        ├ YourAppName
        │     ├ migrations
        │     │     └ __init__.py
        │     ├ __init__.py
        │     ├ admin.py
        │     ├ apps.py
        │     ├ models.py
        │     ├ tests.py
        │     ├ urls.py
        │     └ views.py
        ├ collected_static
        ├ media
        ├ static
        │     ├ css
        │     ├ images
        │     └ js
        └ templates

YourProjectName/settings.pyを書き変える

書き換えるというよりも作り変えるといったほうが的確かもしれません。

YourProjectName/settings/base.py
import os

BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

INSTALLED_APPS = [
    'YourAppName',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'YourProjectName.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'django.template.context_processors.static',
            ],
        },
    },
]

WSGI_APPLICATION = 'YourProjectName.wsgi.application'

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'Asia/Tokyo'

USE_I18N = True

USE_L10N = True

USE_TZ = True

STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
STATIC_ROOT = os.path.join(BASE_DIR, 'collected_static')

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

YourProjectName/settings/local.py
from .base import *

SECRET_KEY = 'Secret Key Written on settings.py'

DEBUG = True

ALLOWED_HOSTS = []

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
YoutProjectName/settings/production.py
from .base import *

SECRET_KEY = 'Secret Key Written on settings.py'

DEBUG = False

ALLOWED_HOSTS = ['YourHostName']

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

詳しくは後日追記。

トップディレクトリにcollected_staticフォルダを作成

理由は後にわかります。

準備完了段階でのディレクトリ構造

YourProjekutName
        ├ YourProjectName
        │     ├ __init__.py
        │     ├ settings
        │     │     ├ __init__.py
        │     │     ├ base.py
        │     │     ├ local.py
        │     │     └ production.py
        │     ├ urls.py
        │     └ wsgi.py
        ├ YourAppName
        │     ├ migrations
        │     │     └ __init__.py
        │     ├ __init__.py
        │     ├ admin.py
        │     ├ apps.py
        │     ├ models.py
        │     ├ tests.py
        │     ├ urls.py
        │     └ views.py
        ├ collected_static
        ├ media
        ├ static
        │     ├ css
        │     ├ images
        │     └ js
        └ templates

GitHubにPush

開発環境から本番環境にデータを移すときに今後の開発の事も考えGitHubを使用します。
.gitignoreで__pychache__、db.sqlite3、collected_static/*を指定して、gitの対象から除外しておくことをおすすめします。

サーバーでの操作

準備も大変だったかと思いますが、これからが本番です。
一応書いておきますが、これからはサーバー上での操作です。

ファイアーウォールの設定

80番ポートを開けてください。
sudo systemctl restart で必ずファイアーウォールのサービスを再起動してください。

Nginxのインストール

sudo apt install nginx

Nginxの設定

この段階で、ブラウザにドメインを打ち込むとWelcome to nginx!と出てくるはずです。
出てこなかった場合、インストールに失敗したか、ファイアーウォールの設定がうまく行っていないか、ドメインやネームサーバー周りの設定がうまく行っていない可能性があります。

まず、次の部分を編集してください。

/etc/nginx/nginx.conf
# user www-data;
user webmaster;

以下のファイルを新規作成してください。
sudo権限がないとPermission deniedとかって出てきます。
;(セミコロン)を絶対に忘れないでください。

/etc/nginx/sites-available/server
server {
    server_name YourHostName;

    location /static {
        alias /home/webmaster/YourProjectName/collected_static;
    }

    location /media {
        alias /home/webmaster/YourProjectName/media;
    }

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header X-Forwarded_For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

ユーザー: webmasterを作成

sudo adduser webmaster

プロジェクトをクローン

先程ローカルでGitHubにPushしたプロジェクトをクローンします。
ここで、注意しなければならないのは、今作ったユーザー(webmaster)のユーザーディレクトリ(/home/webmaster/)上にクローンを作ることです。

Pythonの環境構築

よく、仮想環境を構築する記事がよくあり、その方が好ましいのですが、面倒くさい上、なぜかうまくいかなかったので、普通にやります。

Python3.8をインストール

sudo apt install python3.8 python3.8-dev

pipでパッケージをインストール

ここで必要となるのが、Djangoはもちろん、Gunicornというパッケージです。

python3.8 -m pip install django
python3.8 -m pip install gunicorn

他にも作成したプロジェクトに必要なパッケージがあれば必ずインストールしてください。

今回、Gunicornの動作確認は省略します。

migrateとcollectstatic

python3.8 manage.py makemigrations --settings YourProjectsName.settings.production
python3.8 manage.py migrate --settings YourProjectsName.settings.production
python3.8 manage.py collectstatic --settings YourProjectsName.settings.production

サービスを追加

以下のファイルを新規作成してください。
sudo権限がないとPermission deniedとかって出てきます。

/etc/systemd/system/YourProjectName.service
[Unit]
Description=gunicorn
After=network.target

[Service]
WorkingDirectory=/home/webmaster/YourProjectName
ExecStart=/usr/local/bin/gunicorn --bind 127.0.0.1:8080 YourProjectName.wsgi:application

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl restart nginx
sudo systemctl restart YourProjectName

確認

ブラウザにYourHostNameを打ち込むと、見れるはずです。

HTTPS化

後日書きます。

さいごに

初心者的には結構大変ですが、できないうちに精神的に追い詰められても、途中で投げ出さず、一つ一つ確実にやっていけば必ずできます。
僕もできました。頑張ってください!!
はじめにも書きましたが、なにか抜けているかもしれませんので、ご不明な点がある方や、ミスを発見した方はコメントにお願いします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Chromedriverのバージョンが合わない事によってseleniumでchromeが開けない

概要

seleniumでchromeを開こうとしたら、一瞬だけ画面に表示され、すぐ消えた。
ChromeDriverと、Chromeバージョンが違うとエラー出された。
なんで...

エラー文

"selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 76"
ここでどうしたらよいかわからなかった。

解決策

https://qiita.com/umaruskie/items/f2b4c11b50b34508b64f
ここで紹介されているように、自分が今使っているChromeのバージョンを確認し、そのバージョンをサポートしているChromedriverをダウンロードしないと、seleniumからchromeを開くことができなかった。

解決、解決、めでたしめでたし

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonからGPUの枚数を知る方法〜multiprocessingをpytorchで使う際の注意点〜

はじめに

pytorchでmultiprocessingを利用するとCUDAの初期化で怒られることがあります.

RuntimeError: cuda runtime error (3) : initialization error at /pytorch/aten/src/THC/THCGeneral.cpp:50
THCudaCheck FAIL file=/pytorch/aten/src/THC/THCGeneral.cpp line=50 error=3 : initialization error

なぜ怒られたか

色々調査すると,spawnがどうとか,様々な文献が見当たりましたが,自分の場合はtorch.cuda.device_count()を利用していたことが原因だったようです.

ということで,GPUの枚数をtorch.cuda.device_count()抜きで知りたい.

GPUの枚数をpythonから取得

nvidia-smiに頼ります.
linuxの場合は以下の通り.

import subprocess
msg = subprocess.check_output("nvidia-smi --query-gpu=index --format=csv", shell=True)
n_devices = max(0, len(msg.decode().split("\n")) - 2)

pytorchのCUDA初期化問題には皆様も気をつけてください.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

一流のエンジニアは Mac のメニューバーにこだわる

本記事は Sansan Advent Calendar 2019 13日目の記事です。

一流のプロフェッショナルはどんな些細なことにも気を抜かないものです。
普段使う道具については、特に気をつけていることでしょう。

プロ野球選手であれば、バットやグロープは日々磨き上げていることでしょう。
大工さんであれば、金槌やかんなの手入れを怠る日はないはずです。

プロフェッショナルであるところのエンジニアの皆さんであれば、マウスやキーボードの掃除は日々のルーティンとなっていると思います。
しかし、エンジニアたるもの、メンテナンスすべきは物理的な部分にとどまらないはずです。

合理的に考えれば、メインマシンの作業で最も目にするところ・目に付きやすい所を美しく保つべき、と考えつくのは当然の結果ですね。

では、それはどこでしょうか、デスクトップ?ゴミ箱?ルートディレクトリ?
...
...
...

そうですね、Mac のメニューバーですね!

ということで前置きが長くなりましたが、ここでは、誰でも簡単に Mac のメニューバーの中でもステータスメニューをシンプルかつ機能的に仕上げるノウハウを紹介していきます。
Windows や Linux がメインマシンの方は直接参考にしづらいと思いますが、考え方は同じですので、ぜひ参考にしてみてください。
(ちなみにタイトルはアオリです。僕は一介のデータエンジニアですのでご了承ください。)

ステータスメニューの乱れは、心の乱れ

前述のとおり、ステータスメニューは Mac の画面の中でも最も目にする機会の多い部分の一つです。
この一番目立つ部分がデフォルトのまま、使わないシステム関連のアイコンが出しっぱなしになって、長々と表示されているのは非常に見苦しいものです。
ステータスメニューの乱れは、心の乱れと捉えて、真摯に向き合っていきましょう。

image.png
こっちはひどく散らかったステータスメニュー!

洗練されたステータスメニューを手に入れる方法を知ろう

さて、洗練されたステータスメニューに近づくための近道は、表示するアイコンをできるだけ少なくするです。シンプルですね。
しかし、シンプルなものほど奥深いものです。
ステータスメニューというのは、どの画面でも一律に画面の上端(もしくは下端)という特等席で、情報を表示し気軽なアクセスを提供してくれている、
言わば聖域です。

ここをうまく活用してこそプロフェッショナルと呼ばれるにふさわしいとは思いませんか?
この記事でより高みを目指しましょう。次節にその答えは存在します。

【方法 1】 システム関連アイコンを取捨選択する

回りくどい表現は飽きてきていると思うので、以降は淡々と説明します。
1番手っ取り早いのは、Commandキーを押したままアイコンをメニューバーの外にドラッグすることです。
また、誤って消してしまったときは、「システム環境設定」から表示したい項目を開き、「メニューバーに表示」にチェックをすれば再表示されます。
スクリーンショット 2019-12-13 21.48.30.png
Bluetooth の設定画面の例

全部消してしまってもいいですが、以下のアイコンは時々使うので残しておくと便利かもしれません。
- 音量
- Bluetooth
- ネットワーク情報
- バッテリー残量

【方法 2】 アプリでアイコンを隠す

実際整理すると消せるものはごく限られると思います。
そこで、登場するのがBartender 3です。

Bartender はボタン一つでアイコンを隠してくれるアプリです。

「常に表示するアイコン」と「隠すアイコン」とボタン押下で切り替えてくれるもので、普段よくみる時計などは常に表示にしておき、たまに使うVPN接続アイコンは隠しておくといった使い分けができるようになります。
slider-demo.gif
ワンタップですぐ表示

Bartender は有料アプリなのですが、4週間はトライアルできます。
また、サブスクリプションではなく、買い切りなのでランニングコストがかからないのもありがたいです。

ステータスメニューを最大限活用する

ここまででステータスメニューをシンプルにする、という目標は達成出来たと思います。

しかし、ステータスメニューの真価はここからです。
よりステータスメニューを使い倒していくノウハウをピックアップして紹介していきます。

Google 日本語のアイコンをクールにする

IME として Google 日本語をお使いの人は多いと思いますが、デフォルトだと微妙にダサいと感じる人も多いかと思います。
実はこのアイコンは任意の画像に差し替えることが出来ます。
image.png
目立たないように赤と青の点の画像に差し替えています

手順については以下のページにて、ご参照ください。かなり丁寧で解説されています。
https://lovemac.jp/2011

好きなタイムゾーンの時刻を表示する

開発をしているとかなり頻繁に UTC の時刻表記に出くわすので、ステータスメニューに表示させる Times というアプリを使っています。
image.png
UTC と ニューヨークの時刻を表示

時刻をクリックしてカレンダーを表示できるようにする

これは使ってる方も多いと思いますが、popCalendar というアプリを利用しています。
image.png
時計をクリックするとWindows ライクにカレンダーが展開

...と思ってググったら、 App Store から消えてますね...

一応公式ではなさそうなダウンロードリンクは見つけましたがちょっとアレなので、自己責任で探してみてください。
かなり便利なアプリなだけに残念です。

Python で好きな情報を表示する

実は Python でステータスメニューをいじれる rumps というライブラリがあります。
https://github.com/jaredks/rumps

image.png
以前社内ハッカソンで「定時まであと何時間」「現在の USD/JPY」を切り替えられるアプリを作ったのですが、
久々に動かしてみるとレート取得部分だけ何故かうまく動きませんでした...

なので、タイマー部分だけ抜粋して掲載します。

死ぬほど拙いコードなので、深く考えず参考程度としてください。

go_home_timer.py
import rumps
import datetime

TIMER_MODE = 'timer'
go_home_time = '18:00:00'

ICON_NEUTRAL = 'clear.png'

@rumps.clicked(u'残り勤務時間を表示')
def switchTimer(sender):
    global display_state
    display_state = TIMER_MODE

@rumps.timer(1)
def dispTimer(sender):
    app.icon = ICON_NEUTRAL
    app.title = '残り時間:' + str(datetime.datetime.strptime(datetime.datetime.now().strftime('%Y/%m/%d ') + go_home_time, '%Y/%m/%d %H:%M:%S') - datetime.datetime.now()).split('.')[0]

if __name__ == "__main__":
    display_state= TIMER_MODE
    app = rumps.App("GO_HOME_TIMER", icon=ICON_NEUTRAL, title='Initializing...')
    app.run()

image.png
残り時間もきちんと把握しておくのがプロフェッショナルというものです

まとめ

ステータスメニューは個性が出るところだと思います。
なので、ぶっちゃけ散らかってても、そんなに生産性とかプロ意識とかには関係ないと思ってます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonでQuaternionを使う ~ numpy-quaternion ~

はじめに

Pythonでクォータニオンを扱うライブラリはpyquaternionとnumpy-quaternionが世界でのトップ2のようですが,日本ではpyquaternionの参考ページを作った人が最初にいたからか,巷に溢れているPythonでのクォータニオン計算はpyquaternionばっか(しかない?)です.

しかし,numpy-quaternionのほうが計算コストが低そうです.
参考: https://www.theoj.org/joss-papers/joss.00787/10.21105.joss.00787.pdf

また,githubのスターの数を比べてもnumpy-quaternionのほうが多いです(2019/12/13時点でnumpy-quaternionが276,pyquaternionが138).

numpyとの親和性も一括変換の扱いやすさなどでpyquaternionよりも良い気がします.使ってみませんか?

githubのリポジトリ: https://github.com/moble/quaternion
ドキュメント: https://quaternion.readthedocs.io/en/latest/
PyPI: https://pypi.org/project/numpy-quaternion/

インストール

pip install numpy-quaternion

で簡単に入ります.

また,

import quaternion

でライブラリがインポートできます.

クォータニオンの表記

quat = (w, x, y, z) = \left(\underbrace{\cos\frac{\theta}{2}}_{実数部},\ \underbrace{\lambda_x\sin\frac{\theta}{2},\ \lambda_y\sin\frac{\theta}{2},\ \lambda_z\sin\frac{\theta}{2}}_{虚数(ベクトル)部}\right)

使い方

以下,

import numpy as np
import quaternion

とします.

クォータニオンを作る

quat = np.quaternion(w, x, y, z)

numpyの属性にquaternionが追加されます.
例えば,

print(np.quaternion(1,0,0,0))
# -> quaternion(1, 0, 0, 0)

type(np.quaternion(1,0,0,0))
# -> quaternion.quaternion

となります.

クォータニオンの合成

q1 = np.quaternion(1, 2, 3, 4)
q2 = np.quaternion(4, 5, 6, 7)
print(q1*q2)
# -> quaternion(-52, 10, 24, 20)

掛け算が定義されています.一応他の四則演算も定義されていますが,和とか使うことありますかね…C++のEigenでは定義されていません.

np.quaternionのメソッド

一部のみ取り上げます.球面関数関係のものや,線形補間などは取り上げていません.
参考: https://quaternion.readthedocs.io/en/latest/_autosummary/quaternion.html

メンバ変数

メンバ変数 機能
w 実数部の要素
x 虚数部の最初の要素
y 虚数部の2番目の要素
z 虚数部の3番目の要素
components (w,x,y,z)がnumpy.arrayで返ってくる
imag 虚数部(x,y,z)がnumpy.arrayで返ってくる
vec 虚数部(x,y,z)がnumpy.arrayで返ってくる
real 実数部(w)が返ってくる

メンバ関数

メンバ関数 機能
abs() クォータニオン(ユークリッド距離)の絶対値
absolute() クォータニオンの絶対値
angle() 回転角度
conj() クォータニオンの複素共役を返す
conjugate() クォータニオンの複素共役を返す
equal(quat) 引数の中身のクォータニオンと等しいか
exp exponetialを返す($e^q$)
inverse() 逆クォータニオンを返す
isfinite() 全ての要素が有限か
ininf() 1つでもinfの要素が存在するか
innan() 1つでもnanの要素が存在するか
log() クォータニオンのログを返す
nonzero() 全ての要素が0か
norm() クォータニオンのCayley norm(絶対値の二乗のルート)
normalized() 正規化したクォータニオンを返す
notequal(quat) 引数の中身のクォータニオンと等しくないか
sqrt() クォータニオンのsquare-root($quat=q*q$を満たす$q$)を返す
square() クォータニオンの2乗$(quat*quat)$を返す

quaternionのメソッド

この辺のメソッドは1次元のものではなく,多次元配列にも利用できるのがポイント.最後の次元の大きさがクォータニオンを要求するものなら4,3次元のものなら3次元,3x3なら最後の2次元が3x3にするなどはしないといけない.

メンバ関数 機能
quaternion.as_quat_array(a) numpy.arrayをquaternionに変換.aの最後の次元のサイズは4でないといけない
quaternion.as_float_array(a) numpy.quaternionをnumpy.arrayに変換.出力の次元は入力より1大きい.
quaternion.from_float_array(a) as_quat_arrayと同じ
quaternion.as_rotation_matrix(q) numpy.quaternionを3x3の回転行列に変換.
quaternion.from_rotation_matrix(rot, nonorthogonal=True) 3x3の回転行列をnumpy.quaternionに変換
quaternion.as_rotation_vector(q) クォータニオンから回転軸を求める.出力の最後の次元の大きさは3.
quaternion.from_rotation_vector(rot) サイズ3の回転軸からクォータニオンに変換する.
quaternion.as_euler_angles(q) クォータニオンからオイラー角に変換.オイラー角の変換順などはドキュメント参照
quaternion.from_euler_angles(alpha_beta_gamma, beta=None, gamma=None) オイラー角からクォータニオンに変換.オイラー角の変換順などはドキュメント参照
quaternion.rotate_vectors(R, v, axis=-1) Rはクォータニオン,vはベクトル.クォータニオンに応じてベクトルを回転させる.
quaternion.allclose(a, b, rtol=8.881784197001252e-16, atol=0.0, equal_nan=False, verbose=False) 2つのクォータニオンを比較する
quaternion.integrate_angular_velocity(Omega, t0, t1, R0=None, tolerance=1e-12) 角速度に応じて回転させる

公式ドキュメント

https://quaternion.readthedocs.io/en/latest/index.html

ちなみにこのライブラリの作者はオイラー角が大ッキライみたいです.使ってる人見たらやめろ!って言ってあげてその場から立ち去ってお母さんにちくっちゃえ!とか書いてあります

最後に

Pythonでクォータニオンを扱う需要ってどこにあるんでしょうか?UnityはC#だしそもそもUnityの機能使えって話だし,ロボットではPythonは遅すぎて論外だし…

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonで音を鳴らす方法を詳しめに解説

人間という生き物は自分で合成音声読み上げソフトを作りたい生き物です。
これは仕方のないことです。
パスカルも言ってます、人は考える葦だって。
ほらね?(は?)

まあ、とりあえずその下調べみたいなニュアンスでpythonで音を鳴らす実装を試していきます。

Chapter.0 使用言語・モジュール

  • 言語:Python
  • モジュール
    • numpy(sinやπをつかうので)
    • matplotlib(波形を描画したいなら)
    • wave(.wavファイルの入出力に)
    • struct(waveで.wavファイルにする際に波形のデータをバイナリ化するのに使います)
    • pyaudio(音を鳴らすのに使いますが、Python3.7だとインストールがめんどくさいので最悪使わなくても全然大丈夫)
python
import numpy as np
import matplotlib.pyplot as pl
import wave
import struct
import pyaudio

Jupyter notebookならもうちょっと楽に音を鳴らせるかもしれない(詳しくは知らない)ですが、まあいいや。

Chapter1. 音を式で表現せねば…。

みなさん、音って何か知ってます?
音は周期的(?)な空気の密度の変化みたいなもんです。
要するに波です。波と言えばsin,cosですね。やった!
結論から言えば、下記のような式の正弦波を今回は使います。
sin(2πnt/s)
note_hz=n
sample_hz=s

python
sec = 1 #1秒
note_hz = 440 #ラの音の周波数
sample_hz = 44100 #サンプリング周波数
t = np.arange(0, sample_hz * sec) #1秒分の時間の配列を確保
wv = np.sin(2 * np.pi * note_hz * t/sample_hz)

tは1秒間の時間を表現していて、上の場合44100個の要素の1次元配列です。
私たちの住む世界の情報は連続値(アナログ)ですが、残念ながらパソコンでは離散的(デジタル)なデータしか扱えません。
なので、1秒を44100個に分割して表現するのです。
サンプリング周波数.jpg
(ちなみに44100hzというサンプリング周波数は、CDのサンプリング周波数の規格で、人の可聴域の約二倍の数字にしてあります。なぜ二倍かというのはナイキスト周波数とググりましょう。)

サインの中身は2πnt/sとなっています。
t/sample_hz=t/s は、0,1,2,...,44100 と増えていく s=44100で割ることによって、0,1/44100,2/44100,...,44099/44100,1 と、「徐々(1/44100ずつ)に増える一秒間」を表現しています。

一旦note_hz=n を無視してnp.sin(2 * np.pi * t/sample_hz)=sin(2πt/s) を見てみると、t/s は0→1に増える変数(というよりは時間の関数?)と考えられるので、sinの中身の2πt/sは、0→2πに増えることが分かります。
つまり、sin(2πt/s)は単位円を一秒でちょうど一周する関数(一秒で一回振動する波)になります。
正弦波.jpg
1秒で一回振動するということは、この波の周波数は1〔Hz=1/s〕です。
しかし、周波数1では音には聞こえません。
そこで登場するのが、note_hz=n です。

nを2πt/s にかけるだけで、自由自在に波の周波数を変化させることができます。
例えば、n=440とすると、sin(2πnt/s)は一秒間に440回振動する波(音でいえば"ラ")になります。

なんとこれでプログラム上での音の表現は完了してしまいました。
上に貼ったプログラムをもう一度コピペしておきますね。

python
sec = 1 #1秒
note_hz = 440 #ラの音の周波数
sample_hz = 44100 #サンプリング周波数
t = np.arange(0, sample_hz * sec) #1秒分の時間の配列を確保
wv = np.sin(2 * np.pi * note_hz * t/sample_hz)

Chapter2.プログラムで表現した音を.wavに出力しましょ。そうしましょ。

ここからの流れを説明すると、以下の通りです。

  1. 作った音を.wavファイルとして出力する。
    1. 音のデータをstructモジュールでバイナリ化する。
    2. バイナリ化されたデータを、waveモジュールで.wavファイルとして出力。 2.作った音をプログラム上で鳴らす。(任意)
    3. 作った.wavファイルをwaveモジュールで開く
    4. pyaudioモジュールで鳴らす(任意)
  2. 音の波形をmatplotlib.pyplotモジュールでグラフとして表示する。(任意)

3.に関しては波形が気にならない人はやんなくていいです全然。
2.は、pyaudioというモジュールを使うんですが、Python3.7系だとインストールがめんどくさい(インストールしたい場合は、このページの最後の参考サイトを参照してください)ので、1.で作成した.wavファイルをWindows Media Playerなどで鳴らせばいいです。

では.wavとして出力する方法を説明していきます。

1.バイナリ化

バイナリ化です。
バイナリ化っていうのは、データを二進数にすることですね。
waveモジュールを使う際、バイナリ化しないと.wavファイルへの書き込みができないらしいです。多分。
なのでバイナリ化しましょう!

では先に答えから貼ります。

python
max_num = 32767.0 / max(wv) #バイナリ化の下準備の下準備
wv16 = [int(x * max_num) for x in wv] #バイナリ化の下準備
bi_wv = struct.pack("h" * len(wv16), *wv16) #バイナリ化

こんな感じです。
(というかこれ、参考にしたサイトのほぼコピペみたいなもんだけど、コピペ禁止的なマナーとかあるのだろうか…?まあいいや。)

wv=W, x=「Wの子要素のそれぞれ」= w として、[int(x * max_num) for x in wv]の中身を見ていきます。
Wのそれぞれの子要素wで、
x * max_num
=x * 32767.0 / max(wv)
= w・32767/max(W)
= 32767・(w/max(W))
と表せます。
要するに、波形データの一つ一つの値wと波形データの最大値max(W)の比をとって、32767をかけています。

32767って何の数字だよ!って思いますよね、わかります。
これは、16bitのデータ(16桁の2進数で表現されたデータ)のとりうる値が、-32768~32767であることからきています。(2の16乗が65536で、その半分の数が32768だから……うっ頭がっっっ)
w/max(W)がとりうる値は-1~1、それに32767をかけることで* 32767・(w/max(W)) は *-32767~32767 の値をとり、音の波形データを16bitの中にまんべんなく(というよりピッタリ?)収まるようにしています。
そうしてできるのがwv16です。ふぅ…。

そしてバイナリ化のコードbi_wv = struct.pack("h" * len(wv16), *wv16)
正直僕はこれについて全然わかっていません。コピペです。
とりあえず、structバイナリのstruct.packはバイナリ形式への変換を行ってくれるもので、第一引数の"h"は、2byte(16bit)整数のフォーマットらしい。へぇ。

はい、バイナリ化終了!

2.waveモジュールで.wavファイルを出力

またしても先に答えを貼ります。

python
file = wave.open('sin_wave.wav', mode='wb') #sin_wave.wavを書き込みモードで開く。(ファイルが存在しなければ新しく作成する。)
param = (1,2,sample_hz,len(bi_wv),'NONE','not compressed') #パラメータ
file.setparams(param) #パラメータの設定
file.writeframes(bi_wv) #データの書き込み
file.close #ファイルを閉じる

こんな感じです。
wave.open()で、ファイルを開きます。
第一引数でファイルの名前を指定し、第二引数のmode=で書き込みモード('wb')か読み込みモード('rb')を設定しましょう。

wave.setparams()で.wavファイルのパラメータを設定します。
パラメータ(param)は左から順に、

  • チャンネル数( ステレオ→2、モノラル→1 )
  • サンプルサイズ〔byte〕(今回は2byte)
  • サンプリング周波数 
  • フレーム数(今回でいえばt配列の個数と同じ)
  • 圧縮形式('NONE'だけがサポートされている。それって存在意義あるんか…?)
  • 圧縮形式を人に判読可能にしたもの(圧縮形式'NONE'に対して'not compressed'が返される。)

です。
そしたらバイナリ化したデータ(bi_wv)を書き込んで、ファイルを閉じます。
ファイル閉じるの忘れがちなんだよね…。

よし、できた!!
(端末やコマンドプロンプトでファイルを実行してみて、.wavファイルが生成されているか確認しましょう!)

Chapter3.めんどいからプログラム上で音鳴らしたいよな!

なので、まずwaveモジュールでさっき作ったファイルを開きます。

python
file = wave.open('sin_wave.wav', mode='rb')

これで開けます。
ちゃんと読み込みモードになってますね。
file = wave.open('sin_wave.wav', mode='rb')fileという部分は変数を表してますので、別の名前でも大丈夫です。fairuとかwave_no_kiwami_otomeとか、なんでも。
まあ一応言っといただけです。
僕が初心者のころfileっていう名前じゃなきゃいけないのかな?って勘違いしてたので。

そしたらpyaudioモジュールで音を鳴らしていきます。

python
p = pyaudio.PyAudio() #pyaudioのインスタンス化
stream = p.open(
    format = p.get_format_from_width(file.getsampwidth()),
    channels = wr.getnchannels(),
    rate = wr.getframerate(),
    output = True
    ) #音を録音したり再生したりするためのストリームを作る。
file.rewind() #ポインタを先頭に戻す。
chunk = 1024 #よくわかりませんが公式ドキュメントがこうしてました。
data = file.readframes(chunk) #chunk分(1024個分)のフレーム(音の波形のデータ)を読み込む。
while data:
    stream.write(data) #ストリームにデータを書き込むことで音を鳴らす。
    data = file.readframes(chunk) #新しくchunk分のフレームを読み込む。
stream.close() #ストリームを閉じる。
p.terminate() #PyAudioを閉じる。

上の通り、手順は
1.pyaudioを開く、2.ストリームを開く、3.ストリームにデータを書き込んで音を鳴らす、4.ストリームを閉じる、5.pyaudioを閉じる
という感じです。

Chapter4.波形の表示

いやぁ、記事がこんなに長くなるとは。
僕もう疲れちゃったよ、パトラッシュ。
というわけでコードをバーンと貼っちゃいます。

python
pl.plot(t,wv)
pl.show()

なんてシンプル!
matplotlib.pyplotはいっぱい記事あるんで特に何も言いません。

終わりに

ここまで読んでくれてありがとうございます!
生涯2つ目のQiita記事にしては頑張ったぜ…。

それにしても人工音声合成ソフトは果たして自分で作れるのだろうか…。

参考サイト・文献

最終的なコード

python
import numpy as np
import matplotlib.pyplot as pl
import wave
import struct
import pyaudio

#Chapter1
sec = 1 #1秒
note_hz = 440 #ラの音の周波数
sample_hz = 44100 #サンプリング周波数
t = np.arange(0, sample_hz * sec) #1秒分の時間の配列を確保
wv = np.sin(2 * np.pi * note_hz * t/sample_hz)


#Chapter2
max_num = 32767.0 / max(wv) #バイナリ化の下準備の下準備
wv16 = [int(x * max_num) for x in wv] #バイナリ化の下準備
bi_wv = struct.pack("h" * len(wv16), *wv16) #バイナリ化

file = wave.open('sin_wave.wav', mode='wb') #sin_wave.wavを書き込みモードで開く。(ファイルが存在しなければ新しく作成する。)
param = (1,2,sample_hz,len(bi_wv),'NONE','not compressed') #パラメータ
file.setparams(param) #パラメータの設定
file.writeframes(bi_wv) #データの書き込み
file.close #ファイルを閉じる

#Chapter3
file = wave.open('sin_wave.wav', mode='rb')

p = pyaudio.PyAudio()
stream = p.open(
    format = p.get_format_from_width(file.getsampwidth()),
    channels = file.getnchannels(),
    rate = file.getframerate(),
    output = True
    )
chunk = 1024
file.rewind()
data = file.readframes(chunk)
while data:
    stream.write(data)
    data = file.readframes(chunk)
stream.close()
p.terminate()

#Chapter4
pl.plot(t,wv)
pl.show()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python × Flask × PyTorch 数字認識Webアプリのお手軽構築

Python、Flask、PyTorchを使って画像認識アプリを作ってみます。
この3つを組み合わせれば、お手軽かつ爆速でデモアプリを作れます。

前置き

Flaskとは

Python用のWebフレームワークです。
PythonのWebフレームワークはDjangoが有名ですが、Flaskは軽量を売りにしています。Djangoに比べると機能や拡張ライブラリは少ないですが、制約がある分コードもシンプルになり、お手軽にアプリケーションを作成することができます。
環境構築も簡単にできるのでWebアプリケーションのプロトタイプを作るのに向いています。

Flaskと画像処理、機械学習との親和性の良さ

Pythonは機械学習関連のライブラリが充実しており、デファクトスタンダードになっているのは周知のことです。またPythonはOpenCVやPillow(PIL)などの画像処理ライブラリも充実しており、ネット上の情報も豊富です。
こういった背景もありPython × Flaskでやると機械学習ライブラリ、画像処理ライブラリの利用がとてもやりやすく簡単にアプリケーションを作れます。

今回のゴール

ブラウザから手書き数字画像をアップロードすると、数字を認識して結果を表示するアプリケーションを作ってみます。
2019-12-13_20h51_28.png

アプリケーションの構成

機械学習モデルの構築

今回はPyTorchを使ってMNISTの手書き数字認識モデルを作りました。

Google ColaboratoryでPyTorchでMNISTを学習したモデルを保存し、それを読み出して使う簡単サンプル - 人工知能プログラミングやってくブログ
この記事を参考に学習モデルを作ります。
動かすと1,725,616バイトのmnist_cnn.ptができました。

環境の構築

Flask環境の構築はpipがインストールしてあれば pip install Flask で一発です。
今回はPillow(PIL)、PyTorchも使っているので、これもインストールしておきます。

Webアプリの構築

ディレクトリ、ファイル構成は以下のようになります。

├── mnist_cnn.pt … 手書き数字認識モデル
├── predict.py … メインのスクリプト。ファイルのアップロードと画像判定を行う
├── static … アップロードしたファイルの配置先
│   ├── 20191213210438.png … ここにアップロードしたファイルが保存される
│   ├── 20191213210253.png
│   └── 20191213210341.png
├── templates … htmlテンプレートの保存先
     ├── index.html

predict.pyの内容です。
機械学習モデルの定義・ロードとWebアプリの処理を記述しています。
モデル定義、画像の前処理についての詳細を知りたい方は以下の記事を参照してください。
Pytorch×MNIST手書き数字認識 PNG画像を入力に予測してみる - Qiita

predict.py
# 必要なモジュールを読み込む
# Flask関連
from flask import Flask, render_template, request, redirect, url_for, abort

# PyTorch関連
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
from torchvision import datasets, transforms

# Pillow(PIL)、datetime
from PIL import Image, ImageOps
from datetime import datetime

# モデルの定義
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 20, 5, 1)
        self.conv2 = nn.Conv2d(20, 50, 5, 1)
        self.fc1 = nn.Linear(4 * 4 * 50, 500)
        self.fc2 = nn.Linear(500, 10)

    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = F.max_pool2d(x, 2, 2)
        x = F.relu(self.conv2(x))
        x = F.max_pool2d(x, 2, 2)
        x = x.view(-1, 4 * 4 * 50)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return F.log_softmax(x, dim=1)


device = torch.device("cpu")
model = 0
model = Net().to(device)
# 学習モデルをロードする
model.load_state_dict(
    torch.load("./mnist_cnn.pt", map_location=lambda storage, loc: storage)
)
model = model.eval()

app = Flask(__name__)


@app.route("/", methods=["GET", "POST"])
def upload_file():
    if request.method == "GET":
        return render_template("index.html")
    if request.method == "POST":
        # アプロードされたファイルをいったん保存する
        f = request.files["file"]
        filepath = "./static/" + datetime.now().strftime("%Y%m%d%H%M%S") + ".png"
        f.save(filepath)
        # 画像ファイルを読み込む
        image = Image.open(filepath)
        # PyTorchで扱えるように変換(リサイズ、白黒反転、正規化、次元追加)
        image = ImageOps.invert(image.convert("L")).resize((28, 28))
        transform = transforms.Compose(
            [transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))]
        )
        image = transform(image).unsqueeze(0)
        # 予測を実施
        output = model(image)
        _, prediction = torch.max(output, 1)
        result = prediction[0].item()

        return render_template("index.html", filepath=filepath, result=result)


if __name__ == "__main__":
    app.run(debug=True)

index.htmlの内容です。
このHTMLテンプレートの中にファイルアップロードと認識結果表示を記述してあります。

index.html
<html>
    <body>
        {% if result %}
          <IMG SRC="{{filepath}} " BORDER="1"> 認識結果は {{result}} です<BR>
          <HR>
        {% endif %}
        ファイルを選択して送信してください<BR>
        <form action = "./" method = "POST" 
           enctype = "multipart/form-data">
           <input type = "file" name = "file" />
           <input type = "submit"/>
        </form>
     </body>
</html>

起動と動作確認

python predicy.pyを実行するとFlaskのWebサーバが起動してアプリが動き始めます。ちなみにFlaskのデフォルトポートは5000番です。
http://localhostかホスト名:5000/でアクセスするとWebアプリケーションが表示されます。
手書き数字をアップロードすると
2019-12-13_21h08_00.png
認識結果を表示してくれます。ちゃんと「9」と認識できています。
2019-12-13_21h08_15.png

まとめ

機械学習を使って画像認識するWebアプリケーションを作る、と聞くと難しそうでコードも多く複雑になりそうですが、Flaskを使えば本当にお手軽にできます。
機械学習モデルは作って終わりではなく、まずいろんな人に使ってもらってなんぼかと思います。
ただコマンドベースだと、非エンジニアの人は使えないし、いろいろ試してみるのも難しいです。
そういう時にFlaskを使ってサクッとプロトタイプを作ってしまうのがおすすめです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【2020年版】AWSのEC2にPython3をインストールする方法

先日、AWSのEC2にPython3をインストールする機会がありました。

その時に「AWS EC2 Python3」でGoogle検索したのですが、検索結果の上位に表示されたページの情報がいまいちでした。
具体的には、

  • pyenv
  • virtualenv

を使ってPython3の仮想環境を用意する方法が紹介されていましたが、 Python3の仮想環境を用意する方法は特別な事情がない限りはvenvを使うべきです。
また、Python3をアプリ開発やデータ分析でガッツリ使う場合を除いては、そもそも仮想環境を用意する必要はないと思います。

Python3の仮想環境を用意する方法としてvenvを使う理由は、Pythonの公式ドキュメントで推奨されている唯一の方法だからです。
pyenvを避けるべき理由はこの記事に詳しく書かれているので、興味のある方はこちらをご覧下さい。

また、AWSの公式ドキュメントではvirtualenvを使った方法が紹介されていますが、virtualenvvenvとしてPythonの中に正式に取り込まれたPython3.3以降においては積極的に使う理由はないと思います。

ということで、改めてAWSのEC2にPython3をインストール方法を紹介したいと思います。と言っても、Pythonの公式ドキュメントで紹介されている方法に従うだけですが…。

前提

  • OS
    • Amazon Linux 2 AMI
    • デフォルトではPython2系のみインストールされている
  • インスタンスタイプ
    • t2.micro

手順

  1. OSのシステムにPython3をインストール

    $ sudo yum update
    $ sudo yum install python3 -y
    

    Pythonを使う機会は限られており、Python本体やパッケージのバージョン管理が必要ない場合はこれで十分だと思います。
    一方、アプリ開発でそれ専用のバージョン管理をしたい場合や、データ分析で専用の環境を用意したい場合などは仮想環境の出番です。

  2. venvを使って仮想環境のPython3をインストール

    $ python3 -m venv myenv
    

    上記のコマンドをホームディレクトリで実行すると、ホームディレクトリ配下にmyenvというディレクトリが作成されます。myenvという名前は好きなように変更して構いません。

  3. Python3の仮想環境を有効化

    $ source myenv/bin/activate
    

    上記のコマンドを実行すると、Python3の仮想環境が有効化されます。

  4. Python3の仮想環境を無効化

    $ deactivate
    

    Python3の仮想環境を抜けたい場合は上記のコマンドを実行するだけです。また、環境をまるっと削除したい場合はmyenvディレクトリを削除すればOKです。

最後に

AWSのEC2にPython3をインストールする方法を紹介しましたが、少なくともLinux系のOSではそのまま当てはまる話しだと思います。
Pythonをほとんど使わない人はOSのシステムにインストールしたPython3を、Pythonをガッツリ使う人はvenvを使って作成したPython3の仮想環境を使うと覚えておいて下さい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerを使って動的なサイトをスクレイピングしてみよう

はじめに

近畿大学 Advent Calendar 2019の13日目の記事です.

はじめに注意なのですが, 基本的にスクレイピングは必要がないならするべきではない最終手段です. 今回はQiitaのタグランキングをスクレイピングしますが, Qiitaにはapiが存在しておりそこにタグランキングを取得するapiが存在しなかった(2019年12/8現在)のでスクレイピングをしました.もしあなたが欲しい情報がapiを用いて取得できるならばapiで取得しましょう. また, スクレイピングする際は接続時に待機時間を設け, 接続時間を空けましょう.

必要なもの

Docker

自分が使ってるバージョンは以下.
Docker version 19.03.5, build 633a0ea

docker-compose.yml

docekr-compsoe.yml
version: '3'

services:
    selenium-hub:
        image: selenium/hub
        container_name: selenium-hub
        ports:
          - "4444:4444"

    chrome:
        image: selenium/node-chrome-debug
        depends_on:
          - selenium-hub
        environment:
          - HUB_PORT_4444_TCP_ADDR=selenium-hub 
          - HUB_PORT_4444_TCP_PORT=4444

    python:
        build: .
        container_name: python
        volumes: 
            - .:/workspace
        command: /bin/bash
        tty: true
        stdin_open: true
FROM python:3.7
WORKDIR /workspace

RUN pip install \
    selenium \
    beautifulsoup4

dockerfileとcompose説明

今さらdokcer-composeやdockerfileとは何かというのは書かないのでわからなければDocker上でElixirのPhoenixとPostgreSQLを使ってみたという記事に詳しめに書いているのでそちらを参考にしてください.

では早速docker-compse.ymlから説明します.

動的サイトをスクレイピングするには現在では基本Selenium一択だと思います.

selenium/hubselenium/node-chrome-debugというイメージを使ってコンテナを作成します.
ここでselenium/node-chrome-debugのほうでenvironmentが設定されています.これがなければスクレイピングできないので注意しましょう.

Dockerfileのほうではpythonの環境を構築しています.RUNコマンドで必要なライブラリをDLしています.

これらのファイルと下にあるコードを同一階層上に配置して, docker-compose up -d --buildを実行すればコンテナが立ち上がりあます.

コード例

from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from bs4 import BeautifulSoup
import pprint

class QiitaGetRanking():
    """
    Qiitaからランキングデータを取得するクラス.
    """

    def get_tag_ranking(self, browser: webdriver) -> dict:
        """
        Qiitaからタグランキングに関する情報を取得する関数.

        Parameters
        ----------
        browser: webdrive
            スクレイピングするためのwebdriverのオブジェクト

        Returns
        -------
        tag_ranking_data: dict
            タグランキングを収めた辞書オブジェクト.
        """
        html = browser.page_source.encode('utf-8')
        soup = BeautifulSoup(html, "html.parser")
        ra_tag_names = soup.find_all(class_='ra-Tag_name pr-1')
        tag_ranking_data = {}
        for i, ra_tag_name in enumerate(ra_tag_names):
            tag_ranking_data[i+1] = [ra_tag_name.text, 
            'https://qiita.com/tags/%s'%(ra_tag_name.text.lower())]
        return tag_ranking_data

if __name__ == "__main__":
    """
    main文. browserはhtmlの取得が終わり次第閉じること.エラーが出てきたときも同様.
    """

    try:
        browser = webdriver.Remote(
            command_executor='http://selenium-hub:4444/wd/hub',
            desired_capabilities=DesiredCapabilities.CHROME)
        print("start scrape")
        browser.get('https://qiita.com')
        # javascriptが全て読み込まれるまで待機. 15秒経っても読み込みが終わらなければタイムアウト判定.
        WebDriverWait(browser, 15).until(EC.presence_of_all_elements_located)
        print("generate object")
        qgr = QiitaGetRanking()
        ranking_data = qgr.get_tag_ranking(browser)
        browser.close()
        browser.quit()
        pprint.pprint(ranking_data)
    except:
        browser.close()
        browser.quit()

コードはいたってシンプルだと思います.dockerでスクレイピングする際はseleniumサーバーを建ててスクレイピングした方がubuntu上にselenium環境を構築するより楽だし軽いです.

webdrriver.Remoteに関しては2.5. リモートWebDriverでSeleniumを使用するを参考にしてみてください.

実行結果

docker psで3つのコンテナが建っていることを確認してから,docker exec -it python python qiita.pyでプログラムを実行してください.

start scrape
generate object
{1: ['Python', 'https://qiita.com/tags/python'],
 2: ['JavaScript', 'https://qiita.com/tags/javascript'],
 3: ['AWS', 'https://qiita.com/tags/aws'],
 4: ['Rails', 'https://qiita.com/tags/rails'],
 5: ['Ruby', 'https://qiita.com/tags/ruby'],
 6: ['初心者', 'https://qiita.com/tags/初心者'],
 7: ['Docker', 'https://qiita.com/tags/docker'],
 8: ['PHP', 'https://qiita.com/tags/php'],
 9: ['Vue.js', 'https://qiita.com/tags/vue.js'],
 10: ['Go', 'https://qiita.com/tags/go']}

こんな感じで表示されていたら完全勝利です.お疲れさまでした.

さいごに

今回はDockerを用いて動的サイトをスクレイピングする方法を紹介しました.皆さんも制限の範囲内でスクレイピングを楽しみましょう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python for .NETの動作を各環境でチェックする

Python for .NETは各種の環境へインストールが可能となっていますが、どの組み合わせで可能なのか今一歩情報がまとまっていません。
やってみて出来たものを中心にまとめていこうと思います。
できたものから順に記述していこうと思います。

Windows 10

これを使うのは、Windows 10での動作がメインと思いますので、厚めに試します。

固定条件として以下とします。

  • pythonnet 最新版 (2.4.0)
  • Anaconda python 3.x

Python.NET @ github

には、以下のように書いてあるので、基本的に、3.7を使っています。

Python 3.8.0 support
Some features are disabled in Python 3.8.0 because of this bug in Python. The error is System.EntryPointNotFoundException : Unable to find an entry point named 'Py_CompileString' in DLL 'python38'. This will be fixed in Python 3.8.1.

.NET Framework 4

早く.NET5が出てほしいところですが、もうしばらく待たないといけないので、レガシーの方から。Windows10にプリインストールの.NET 4.6を利用しています。

.NET環境の準備

特に何もしなくてよさそうです。標準の環境ですから当たりまえですね。

Python環境の準備

これもAnaconda3の環境を入れただけで、特にTweakは必要なかったです。

動作確認

これでエラーが出なければ、ひとまず大丈夫でしょう。

import clr
clr.AddReference("System.Windows.Forms")
from System.Windows.Forms import Form
print("OS : ", Environment.OSVersion.VersionString)
print("Python : " ,sys.version)
print(".NET : ", Environment.Version.ToString())
print("pythonnet : " , clr.__version__)
実行結果
OS :  Microsoft Windows NT 10.0.19041.0
python :  3.7.3 (default, Apr 24 2019, 15:29:51) [MSC v.1915 64 bit (AMD64)]
.NET :  4.0.30319.42000
pythonnet :  2.4.0

Windows 10のバージョンが怪しいですが、そこは。。。

.NET Standard ライブラリ

.NET Core

デスクトップ開発者もそろそろ .NET Coreに移行を考えてもいいんじゃないかというレベルに来ているのかどうか知りませんが、.NET Core 3.1でFrameworkに劣らない機能が利用できるようになっています。
しかし、残念ながらpythonnetではまだ準備ができておらず、.NET CoreからPythonを動かそうという事はできないようです。(そもそも、そんな事許されるわけないじゃん)

Support for .NET Core? #243 @ github

.NET Core (CoreCLR) does not provide reverse pinvoke like .NET Framework on
Windows, neither C++/CLI. Hence the only way to get this working is to
embed .NET Core using C-API, like this is done for Mono. The problem is
that C-API for CoreCLR looks quite different from Mono.

CoreCLRではreverse P/Invokeがサポートされていないから、無理ゲーと書いています。
Pythonから.NET Coreは出来てもいいんじゃない?と思いますが。

しかし!上記は2016年の情報で、そこから以下のissueに状況が記されています。

.NET Core support and CoreCLR embedding - cross-platform API #96 @ github

denfromufa commented on Oct 17, 2018
によると、以下のサポート状況のようです。

Platform .NET-> Python Python -> .NET
Windows Tested Coded (npython.exe)
Linux Tested Tested (npython.exe)
OSX Coded Coded (npython.exe)

Pythonから.NET Coreを呼ぶ場合は、npython限定など、かなり制限がきついようですね。

Linux

Mono

.NET Core

Mac OSX

Mono

.NET Core

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kaitai Struct を使って、バイナリデータのパーサを作ってみる

はじめに

LOCAL学生部アドベントカレンダー11日目

つよつよな人ばかりで「自分弱すぎないか…?」と心配になったので、僕は変化球を投げることにしました。
僕が探した限りでは日本語の文献が存在しないため、今後これについて調べた人はほぼ自動的にこの記事を目にするのではないでしょうか。
そんな皆さんにお願いです。
「コイツ何言ってんだ?間違った情報だらけじゃないか」と思ったら、是非コメントください。
全力で修正します。

Kaitai Struct って何?

概要

公式: kaitai.io

Kaitai Struct は、バイナリデータ構造を記述するために使用される宣言型言語です。

独自の言語で書いたデータ構造をもとに、バイナリデータのパーサのソースコードを自動生成できます。

対応言語(2019年12月2日現在)
  • C++ / STL
  • C#
  • Go (entry-level support)
  • Java
  • JavaScript
  • Lua
  • Perl
  • PHP
  • Python
  • Ruby

license

のちに記述するCompilerとVisualizerはGPLv3+であり、各言語のライブラリはMIT(JSはApache v2)
これって、Compilerを用いて生成したソースコードはGPLに感染するのだろうか…?
詳しい人教えてください。

インストール

Kaitai Struct Compiler (KSC)

インストールに関する詳しい情報はこちら
Macはbrew install kaitai-struct-compilerで一発です。
Windowsは、上記のリンクに飛んでインストーラをダウンロードしてください。

Debian/Ubuntuベースのディストリビューションなら公式の.debリポジトリからパッケージをインストールできます。
# Import GPG key, if you never used any BinTray repos before
sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net --recv 379CE192D401AB61

# Add stable repository
echo "deb https://dl.bintray.com/kaitai-io/debian jessie main" | sudo tee /etc/apt/sources.list.d/kaitai.list
# ... or unstable repository
echo "deb https://dl.bintray.com/kaitai-io/debian_unstable jessie main" | sudo tee /etc/apt/sources.list.d/kaitai.list

sudo apt-get update
sudo apt-get install kaitai-struct-compiler


その他のOSを用いている場合は、ここからCloneしてビルドしましょう。

Kaitai Struct Visualizer (KSV)

これは.ksyファイルのためのシンプルなビジュアライザです。
Rubyで書かれており、gemパッケージとして入手できます。

gem install kaitai-struct-visualizer

(Gitリポジトリ)

使ってみる

有名なファイルについては、公式のgithubリポジトリ.ksyファイルが存在します。
(ここに存在する.ksyファイルを利用する場合、ファイル内のmeta/licenseに記述されたライセンスを確認してください。)
もしあなたが新しい.ksyを書いたなら、プルリクエストを送りましょう。
(kaitai_struct_formats/CONTRIBUTING.md)

例) matrix

ファイルへの保存(np.array)
matrix.py
import numpy as np
import struct


def create_header(*mats: [np.ndarray], magic: bytes = None) -> bytes:
    header = magic
    header += struct.pack('<H', len(mats))
    length = len(header) + 8 * len(mats)
    for mat in mats:
        header += struct.pack('<HH', mat.shape[0], mat.shape[1])
        header += struct.pack('<I', length)
        length += 4 * mat.shape[0] * mat.shape[1]
    return header


mat1 = np.random.randint(-1024, 1024, [3, 3], dtype=np.int32)
mat2 = np.random.randint(-1024, 1024, [5, 9], dtype=np.int32)
mat3 = np.random.randint(-1024, 1024, [2, 2], dtype=np.int32)

with open('test.matrix', 'wb') as o:
    magic = b'THIS IS MAT FILE.\x01\x02'
    o.write(create_header(mat1, mat2, mat3, magic=magic))
    for mat in [mat1, mat2, mat3]:
        for y in mat:
            for x in y:
                o.write(struct.pack('<i', x))


上記のコードで生成されたtest.matrixを、KSを使用して読み込んでみようと思います。

test.matrix
  Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F   
00000000: 4D 41 54 01 02 2F 03 00 03 00 03 00 20 00 00 00    MAT../..........
00000010: 05 00 09 00 44 00 00 00 02 00 02 00 F8 00 00 00    ....D.......x...
00000020: DC FE FF FF 49 01 00 00 A7 FF FF FF 17 02 00 00    \~..I...'.......
00000030: 25 FC FF FF 35 FF FF FF B5 00 00 00 CF FE FF FF    %|..5...5...O~..
00000040: E2 FF FF FF 5D 00 00 00 15 FE FF FF 30 FC FF FF    b...]....~..0|..
00000050: 4C 03 00 00 C1 FF FF FF B0 FD FF FF 31 02 00 00    L...A...0}..1...
00000060: 54 03 00 00 C4 FF FF FF 65 FF FF FF D0 FE FF FF    T...D...e...P~..
00000070: 75 01 00 00 DE FE FF FF ED 00 00 00 ED FC FF FF    u...^~..m...m|..
00000080: BE FD FF FF E5 02 00 00 EC FE FF FF 22 FE FF FF    >}..e...l~.."~..
00000090: C3 02 00 00 11 00 00 00 29 03 00 00 00 01 00 00    C.......).......
000000a0: 78 00 00 00 C4 FC FF FF 4C 02 00 00 88 00 00 00    x...D|..L.......
000000b0: 43 FF FF FF 35 FF FF FF A4 00 00 00 CF 02 00 00    C...5...$...O...
000000c0: 3A FF FF FF 33 FF FF FF BD FE FF FF F9 01 00 00    :...3...=~..y...
000000d0: 22 FF FF FF 3A 02 00 00 7C 00 00 00 15 FF FF FF    "...:...|.......
000000e0: D8 FE FF FF 42 00 00 00 82 02 00 00 24 02 00 00    X~..B.......$...
000000f0: 8A FE FF FF AF FF FF FF EF 02 00 00 96 01 00 00    .~../...o.......
00000100: 83 01 00 00 2F 02 00 00  

ファイルの構造は、頭から
1. b'MAT\x01\x02/'
2. 存在する行列の数(2bytes)
3. 各行列ごとのshapeとoffset((8 * 行列の数)bytes)
4. 行列本体
となっています。

これをmatrix.ksyに記述してみます。

KSY(Kaitai Struct YAML)では、単一のユーザー定義型(公式から直訳)を宣言します。
ユーザー定義型は
- meta
- doc
- seq
- types
- instances
- enums
で構成されます。
すべてを持つ必要はありません。
詳しい情報は公式リファレンスをご覧ください。

meta

meta
meta:
  id: matrix
  endian: le

記述するユーザー定義型の名前をmeta/idに記述します。これは.ksyファイルに必ず存在する必要があります。
meta/endianは構造体で使用するデフォルトのエンディアンを記述します(le/be)

seq

seq
seq:
  - id: magic
    contents: ['MAT', 1, 0x2, '/']
  - id: header_num
    type: u2
  - id: headers
    repeat: expr
    repeat-expr: header_num
    type: header

seqに、データの構造を記述していきます。
idは変数名になります。
データが定数である場合はcontentsに定数を記述します。
値を取得したい場合、typeにデータの型を記述します(詳しくはこちら)。
後述するtypesに記述した型を利用することもできます。ここではheader型を利用しています。
repeatには expr, eos, untilのいずれかを入れることができます(詳しくはこちら)
exprを入れた場合、repeat-exprに繰り返す回数を入れます。

types

types
types:
  header:
    seq:
      - id: shape0
        type: u2
      - id: shape1
        type: u2
      - id: offset
        type: u4
    instances: 
      mat_body:
        pos: offset
        io: _root._io
        type: matrix

  matrix:
    seq:
      - id: dim0
        repeat: expr
        repeat-expr: _parent.shape0
        type: dim1
    types:
      dim1:
        seq:
          - id: dim1
            repeat: expr
            repeat-expr: _parent._parent.shape0
            type: s4

typesには、ユーザー定義型をネストして記述することができます。
header型でinstancesを使用していますが、これはseqのように順番に存在するもの以外のデータを読み込むために使用することができます。

header.instances
instances: 
  mat_body:
    pos: offset
    io: _root._io
    type: matrix

使い方はseqによく似ています。
idはここでいうmat_bodyです。
ioは使用するIOストリームです。
posにはioの頭からのバイト数が入ります。
typeseqの時と同じです。

変数について

一部のフィールド(今回はrepeat-expr, pos, io)では、定数値だけではなく変数を参照できます。
まだ読み込まれていないデータを参照することはできません。
データはツリー構造になっており(ksvを利用するとわかりやすい)、親の要素を_parentで指定できます。
また、一番上の要素を_rootで指定できます。

Visualize

ここまでで、以下のコードを書くことができました。

matrix.ksy
meta:
  id: matrix
  endian: le

seq:
  - id: magic
    contents: ['MAT', 1, 0x2, '/']
  - id: header_num
    type: u2
  - id: headers
    repeat: expr
    repeat-expr: header_num
    type: header

types:
  header:
    seq:
      - id: shape0
        type: u2
      - id: shape1
        type: u2
      - id: offset
        type: u4
    instances: 
      mat_body:
        pos: offset
        io: _root._io
        type: matrix

  matrix:
    seq:
      - id: dim0
        repeat: expr
        repeat-expr: _parent.shape0
        type: dim1
    types:
      dim1:
        seq:
          - id: dim1
            repeat: expr
            repeat-expr: _parent._parent.shape0
            type: s4

これをksv(Kaitai Struct Visualizer)を使用して可視化してみましょう。
使い方はksv <file_to_parse.bin> <format.ksy>です。

shell
$ ksv test.matrix matrix.ksy
ksv
[-] [root]                              00000000: 4d 41 54 01 02 2f 03 00 03 00 03 00 20 00 00 00 | MAT../...... ...
  [.] magic = 4d 41 54 01 02 2f         00000010: 05 00 09 00 44 00 00 00 02 00 02 00 f8 00 00 00 | ....D...........
  [.] header_num = 3                    00000020: dc fe ff ff 49 01 00 00 a7 ff ff ff 17 02 00 00 | ....I...........
  [-] headers (3 = 0x3 entries)         00000030: 25 fc ff ff 35 ff ff ff b5 00 00 00 cf fe ff ff | %...5...........
    [-] 0                               00000040: e2 ff ff ff 5d 00 00 00 15 fe ff ff 30 fc ff ff | ....].......0...
      [.] shape0 = 3                    00000050: 4c 03 00 00 c1 ff ff ff b0 fd ff ff 31 02 00 00 | L...........1...
      [.] shape1 = 3                    00000060: 54 03 00 00 c4 ff ff ff 65 ff ff ff d0 fe ff ff | T.......e.......
      [.] offset = 32                   00000070: 75 01 00 00 de fe ff ff ed 00 00 00 ed fc ff ff | u...............
      [-] mat_body                      00000080: be fd ff ff e5 02 00 00 ec fe ff ff 22 fe ff ff | ............"...
        [-] dim0 (3 = 0x3 entries)      00000090: c3 02 00 00 11 00 00 00 29 03 00 00 00 01 00 00 | ........).......
          [-] 0                         000000a0: 78 00 00 00 c4 fc ff ff 4c 02 00 00 88 00 00 00 | x.......L.......
            [-] dim1 (3 = 0x3 entries)  000000b0: 43 ff ff ff 35 ff ff ff a4 00 00 00 cf 02 00 00 | C...5...........
              [.] 0 = -292              000000c0: 3a ff ff ff 33 ff ff ff bd fe ff ff f9 01 00 00 | :...3...........
              [.] 1 = 329               000000d0: 22 ff ff ff 3a 02 00 00 7c 00 00 00 15 ff ff ff | "...:...|.......
              [.] 2 = -89               000000e0: d8 fe ff ff 42 00 00 00 82 02 00 00 24 02 00 00 | ....B.......$...
          [-] 1                         000000f0: 8a fe ff ff af ff ff ff ef 02 00 00 96 01 00 00 | ................
            [-] dim1 (3 = 0x3 entries)  00000100: 83 01 00 00 2f 02 00 00                         | ..../...        
              [.] 0 = 535
              [.] 1 = -987
              [.] 2 = -203
          [-] 2
            [+] dim1
    [-] 1
      [.] shape0 = 5
      [.] shape1 = 9
      [.] offset = 68
      [-] mat_body
        [+] dim0
    [+] 2

うまく読み込めているようです。

ファイル解凍

本題です。
こちらの記事で圧縮ファイルを作りました。
今回はこの圧縮ファイルをKSを使用して解凍します。
ファイルの構造などは記事をご覧ください。

mcp.ksy
meta:
    id: mcp
    encoding: UTF-8
    endian: le

seq:
  - id: file
    type: file
    repeat: eos
types:
    file:
        seq:
          - id: filename_len
            type: u4
          - id: filebody_len
            type: u4
          - id: filename
            type: str
            size: filename_len
          - id: filebody
            size: filebody_len
            process: zlib

meta/encodingは、type: strで使用するデフォルトのエンコーディングを指定します。
repeat: eosはストリームの最後まで繰り返します。
process: zlibは、読み込んだデータをzlibで解答します。(すごく便利)
processの詳しい情報はこちら

ksc(Kaitai Struct Compiler)を使用してmcp.ksyからコードを生成します。

usage
Usage: kaitai-struct-compiler [options] <file>...

  <file>...                source files (.ksy)
  -t, --target <language>  target languages (graphviz, csharp, all, perl, java, go, cpp_stl, php, lua, python, ruby, javascript)
  -d, --outdir <directory>
                           output directory (filenames will be auto-generated)
  -I, --import-path <directory>:<directory>:...
                           .ksy library search path(s) for imports (see also KSPATH env variable)
  --go-package <package>   Go package (Go only, default: none)
  --java-package <package>
                           Java package (Java only, default: root package)
  --java-from-file-class <class>
                           Java class to be invoked in fromFile() helper (default: io.kaitai.struct.ByteBufferKaitaiStream)
  --dotnet-namespace <namespace>
                           .NET Namespace (.NET only, default: Kaitai)
  --php-namespace <namespace>
                           PHP Namespace (PHP only, default: root package)
  --python-package <package>
                           Python package (Python only, default: root package)
  --opaque-types <value>   opaque types allowed, default: false
  --ksc-exceptions         ksc throws exceptions instead of human-readable error messages
  --ksc-json-output        output compilation results as JSON to stdout
  --verbose <value>        verbose output
  --debug                  enable debugging helpers (mostly used by visualization tools)
  --help                   display this help and exit
  --version                output version information and exit
shell
$ ksc -t python mcp.ksy
mcp.py
# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

from pkg_resources import parse_version
from kaitaistruct import __version__ as ks_version, KaitaiStruct, KaitaiStream, BytesIO
import zlib


if parse_version(ks_version) < parse_version('0.7'):
    raise Exception("Incompatible Kaitai Struct Python API: 0.7 or later is required, but you have %s" % (ks_version))

class Mcp(KaitaiStruct):
    def __init__(self, _io, _parent=None, _root=None):
        self._io = _io
        self._parent = _parent
        self._root = _root if _root else self
        self._read()

    def _read(self):
        self.file = []
        i = 0
        while not self._io.is_eof():
            self.file.append(self._root.File(self._io, self, self._root))
            i += 1


    class File(KaitaiStruct):
        def __init__(self, _io, _parent=None, _root=None):
            self._io = _io
            self._parent = _parent
            self._root = _root if _root else self
            self._read()

        def _read(self):
            self.filename_len = self._io.read_u4le()
            self.filebody_len = self._io.read_u4le()
            self.filename = (self._io.read_bytes(self.filename_len)).decode(u"UTF-8")
            self._raw_filebody = self._io.read_bytes(self.filebody_len)
            self.filebody = zlib.decompress(self._raw_filebody)

mcp.pyが生成されたコードです。
これを使って解凍用のスクリプトを書きましょう。

extract.py
from mcp import Mcp
import os
import sys

mcps = Mcp.from_file(sys.argv[1])
out = 'output/'
if len(sys.argv) >= 3:
    out = sys.argv[2]

for f in mcps.file:
    if os.path.dirname(f.filename):
        os.makedirs(os.path.join(out, os.path.dirname(f.filename)), exist_ok=True)
    with open(os.path.join(out, f.filename), 'wb') as o:
        o.write(f.filebody)

python extract.py <target.mcp> [output_folder]で解答することができます

ファイルの読み込みは、KaitaiStruct.from_file(file_path)で。
バイト列をそのまま読み込みたい場合、KaitaiStruct.from_bytes(bytes)で。
IOストリームの場合はKaitaiStruct.from_io(io)で。

さいごに

KSはかなり便利だと僕は思います。
簡単に記述出来る上好きな言語で利用できるので、新しく覚えるコストがとても少なく済みます。
公式リファレンスは正直読みにくいですが、今後僕のようにKSについて記事を書いてくれる人が増えていくでしょう(多分)。

あなたもKSをつかって「解体」してみませんか?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Hi Py(その1)】とりあえず何かしらを作りたいので、まずは目標を設定する。

【私について】

過去にサポート、ライティング、マーケティング、ディレクション(社内に出来る人がいない為、無知なりに調べてやっていた)などを経験し、
最近はhtml/css/js/php/mysqlの上澄みだけ編集したり、excelを綺麗にまとめて差し上げたりgasを使って業務効率化を計ったりするだけの仕事をやっている。

「何だそれは仕事なのか?」なんて言わないで。

そして底辺なので、たまに副業でwordpressのカスタマイズもする。
 
更に、
・興味のある事がすぐに変わったり
・継続力もなく
・集中力も注意力も散漫なので
・超が付くほど要領も記憶力も悪い上
・めちゃめちゃ怠惰な

ゲロやば倍満人間だけど、「人は、やれる。」という意志と決意を込めての記念すべき初投稿。
 
 
半年前にProgateのPython講座は一通りやり、何かが作れる気もせず全てを忘れるレベルまで来たが、
(気まぐれで)お絵かきする為に購入したiPadの購入を期に、Pythonのお勉強をしようと思い立ったわけである。

ただし音楽を作ったりお絵かきをしたり、ゲームをしたり、政治の勉強もしたいので、更新頻度が低くなる事をあらかじめ宣言しておく。

progateでは構文をふわっと理解しただけだったので、今回は実際にデータを動かして楽しくなろう。というのをテーマに独学で勉強していきたい。
 

<とりあえずの目標設定>

まずは何がしたいのかをはっきりさせる。
と思ったので、やりたい事をわかりやすく出来るよう個人的にやりたい事をメモしていく。
 

【作ってみたいもののアイデア】

①地域別の経済状態把握アプリ

ResasのAPIとLine notifyを使って、知りたい市区町村の経済循環や外国人率、有効求人倍率、一人当たり賃金など、個人的に気になる情報を綺麗にまとめて表示してくれて、line notifyで通知してくれる感じのもの。

(普段は出来ない政治系の話題をLINE openchatでしてるので、LINEに送るようにしようと思っているが、必須ではないので省くかも)

Pythonista3でWeb APIを取得するアプリを作ってみる
http://tarao-mendo.blogspot.com/2018/03/pythonista3-web-api.html

【Pythonista】LineNotifyでLineにメッセージを送る
https://tk-thunder.hateblo.jp/entry/2017/11/28/192929

resus API仕様書
https://opendata.resas-portal.go.jp/docs/api/v1/index.html

  

②Spotifyでのオススメ最適化アプリ

55カ国の音楽を聞いているらしく、ジャンルもバラバラなので、Spotifyのオススメ選曲機能が全く役に立っておらず、情報の流動性も最悪。
脳みそが停滞しているのを日々感じながらspotifyと接しているので、改善したい。

なので、bandcamp API、Soundcloud API、Youtube API、Spotify APIなどで各アカウントを連携させて、その日の気分に分けたオススメを収集出来るようなアプリを作ってみよう。
という事で。

具体的には各ストリーミングサービスにログインした上で、そこでお気に入りに入れたアーティスト名や楽曲名をspotifyの中で検索をかけて、出て来た楽曲の情報と、その日の気分を元に、新たにspotifyでオススメを探して貰えないかなという。

しかし、以前phpで同様の事をしようとしたものの、
そもそもtokenの受け渡しがうまく出来ずに断念したので今回もちょっと怪しくはあるが、せめて地道に向かっていきたい。

Pythonistaのライブラリ追加方法。(Spotipyのimportに必要)
https://piruty2.hatenablog.jp/entry/%3Fp%3D458
spotify API
https://qiita.com/EkatoPgm/items/289b2efcdb5af49843c1

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonの配列っぽいやつらを比べよう

本稿はNCC Advent Calender 2019の14日目記事です.

はじめに

プログラム書くとき,配列って大事です.
しかし,Python 配列っぽいの多すぎ問題があります.
Pythonの標準機能だけでも
listdictsettuple
があります.
加えてnumpyなどのライブラリを使い始めるとさらに似たようなものが増えます.

なので,今回はそいつらを以下の4点でまとめて使い分けができるように解説していきます.

  1. 概説
  2. いいとこ
  3. わるいとこ
  4. 所感

あくまで使い分けのための記事なので,細かい使い方などには触れません.
また,配列が何かはなんとなくわかってる前提で書きます.
さらになるべく簡単な言葉を使って説明します.
* 説明の易化のために,本質とは異なる説明をしている場合があります.

対象読者

  • Pythonを使い始めたけど,配列多すぎて困ってる
  • 他の言語やってたけど,Pythonでの各配列の立ち位置がわからない
  • 実装中,ここどれ使うべきかなってなった人
  • numpyなどのライブラリを使い始めた人

とりあげるもの

Python標準機能

  • list
  • dict
  • set
  • tuple

ライブラリ

numpy

  • numpy.array/numpy.ndarray
  • numpy.matrix

本編

list

概説

listは簡単に言えば一番の基本のやつです.
他言語でいう一般的な配列と同じで,Pythonでは「リスト」と呼ぶのが一般的です.
記号としては[]なので,これで囲まれて,中に,がいっぱいあったらリストだと思ってください.

Pythonではリスト内の型が複数でも構いません.
また,同じ値を何個でも入れることができます.
その他,世間一般に言う配列の機能を持っています.

あとで,dict以下の説明をするときに,よくlistと比較すると思うので,それでlistの性質はそれを見ながら把握してもらえるといいかなと思います.
(普通のやつなのであまり説明がないです.)

いいとこ

  • 扱いが簡単
  • 難しいこと考えず,とりあえずlistにしとけば実装はできる

わるいとこ

  • 次元が多くなるとわかりにくくなる
    • 一個一個の配列の長さをまとめて見れない(numpyshapeみたいなのがない)
  • 参照が0から続く数字のみ

所感

良くも悪くも原点.
numpyとかpandasでも一旦listに変換してから使うこともザラですね.
1次元で長さが不定な場合や,順序にい意味があるが番号に意味がない時もlistです.
ただ多次元ではあまり向かないです.
(そもそも次元が多すぎるような設計をするな)
数値であればnumpy,複合的なデータであればdictと組み合わせるとスッキリします.

dict

概説

いわゆる連想配列ってやつです.
Pythonでは辞書型またはdictionary(略してdict)です.
記号としては{}で,{key: value}にように,一つ一つの要素が:で結ばれます.
例に及んで詳しくは説明しません.レファレンスなどを参照してください.

dictkeyの型はなんでもよかった気がしますが,文字列か整数を使った方がわかりやすいです.
あと中身がいっぱいある時はkeyごと改行すると見やすいです.またインデントは揃えましょう.(jsonみたいに)
例を載せておきます.

ncc = { 'name': 'ncc',
        'full name': 'nakano computer club',
        'estimate': 2015,
        'web site': 'https://meiji-ncc.tech/'
        }

*インデントに関してはいくつか宗派があるのでお好みのものを使用しましょう.
また一行一要素で長すぎる場合は,3個ずつなどにしたほうが見やすい場合もあります.
この辺は内容に合わせて臨機応変に対応しましょう.

いいとこ

  • わかりやすい
  • jsonとの相互利用がしやすい
  • 参照に文字列が使える

わるいとこ

  • 慣れないとエラーのもと
  • 深くなると取り出しづらい
    • dic['first']['second']['third']みたいになっていく

所感

値とその名前が重要になる時はこいつで決まり!
数字でも間隔が不規則な時はdictのほうが扱いやすいです.
あと,pandas(ライブラリ)のDataFrameに変換する前に一旦dictでまとめることも多いです.そのほうが扱いやすいので.
keyvalueを結びつけるイメージで使うといいかと思います.
なんやかんやjsonとの対応が便利だったり..(jsonライブラリ使えば読み書き簡単)

set

概説

set被り禁止のlistというのが端的な表し方です.
記号は{}dictと同じですが,:は使いません.
listと同じように,で値をポンポン並べていきます.
なので,{}で囲まれて,,がいっぱいあったらsetです
厳密には集合を表すものです.
なので集合演算もできます.(ここでは触れませんが)

list[0, 1, 2, 1, 0]のように同じ値を持つことができますがsetではできません.
上記のlistsetに変換すると{0, 1, 2}となります.
これは数字以外でも同じです.

逆を言えば,被りをなくたい時はsetに変換するといけます.
この場合,リストとしてまた扱いたい場合はsetの上からlistに変換してリストに戻す必要があります.
例を書いておきます.

list_duplicate = [0, 1, 2, 2, 1, 0, 3]
list_non_duplicate = list(set(list_duplicate))
print(list_non_duplicate) # out: [0, 1, 2, 3]

いいとこ

  • 被りなく入れられる
  • 集合演算ができる

わるいとこ

  • 要素の順番にルーズ
    • (listから変換した場合,被りが除かれてその分前につめます)
  • 記号がdictと一緒でわかりづらい(若干)

所感

最初からsetで使うことはあまりないです.
被りをなくしたいときや,複数のリストの要素の共通部分などを抽出する時などにリストから変換することが多いです.
なので,実装時に集合的アプローチをとるときに使うものと思っておけばいいと思います.

tuple

概説

tupleちょっとお堅いlistと言うべきでしょうか.
少しクセがあります.
記号は()です.

基本はlistのようなものですが,色々違います.
大まかに言えば,一度作ったものをいじくることができません
後ろに別のタプルを追加することはできます.(1)
タプル自体を丸々別のものに変えることもできます.(2)
その他,要素の書き換えなどはできません.
やや難しいので,実例で示します.

t = (0, 1, 2)
# 後ろにタプルを追加
t += (3, 4) # OK(1)
# タプル自体の書き換え
t = (0, 1, 2) # OK(2)
# 要素の書き換え
t[0] = 1 # Error

代入や,削除などにはそもそもメソッドが用意されてません.
また,要素の書き換えができないので,特定の順番を変えることもできません.
この辺をやるには,listに変換する必要があります.

いいとこ

  • 一回作ったら書き換えできない
  • 順番も作成時のものが常に保証される
  • 挙動を確定させられる
  • 定数として使える

わるいとこ

  • 柔軟性が皆無
  • 扱いづらい
  • エラーのもと

所感

Pythonには定数を表す型(jsでいうconst)がないので,それを擬似的にtupleで行うことができます.
ただ,動的なことはいっさいできないので,あまり使いません.
ライブラリのメソッドの返り値がtupleのことがあるので,そこでつかうぐらいです.


続いてPythonのライブラリであるnumpyにおける配列系の説明に移ります.
その前に,numpyの説明をさっくりします.

(補足)numpyとは

numpyは線形代数でやる行列演算を行えるライブラリです.
配列の要素同士の足し算,引き算.
配列全体に数値をかける時などに使えます.
もっと高度なこともできますが,配列の数値計算が便利になると思っておけば大丈夫です.

numpy.array/numpy.ndarray

概説

numpyの1次元の配列がnumpy.arrayです.
多次元はnumpy.ndarrayになります.
1次元でも多次元でも扱いはあまり変わりません.
こいつはライブラリなので,特定の記号で表すことはできません.
listなどをnumpy.array()で囲んであげると変換されます.
* 多次元でもnumpy.array()で囲みます,numpy.ndarrayでは囲めません.
listとの違いは何と言っても,配列同士の演算ができることです.
ただ,空のnumpy.arrayを作成することはできません.
listから変換しましょう.

import numpy as np

# 通常のlistを定義
list_num0 = [0, 1, 2, 3, 4]

# numpy配列に変換
np_num0 = np.array(list_num0)
print(np_num0) # out: [0 1 2 3 4]

# 直接numpy配列を生成
np_num1 = np.array([5, 6, 7, 8, 9])
print(np_num1) # out: [5 6 7 8 9]

# numpy配列をlistに変換
list_num1 = list(np_num1) 
print(list_num1) # out: [5, 6, 7, 8, 9]

# list,numpy配列それぞれを2倍してみる
list_num0_twice = 2*list_num0
print(list_num0_twice) # out: [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
np_num0_twice = 2*np_num0
print(np_num0_twice) # out: [0 2 4 6 8]

# list, numpy配列でそれぞれ足し算してみる
list_num_add = list_num0 + list_num1
print(list_num_add) # out: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
np_num_add = np_num0 + np_num1
print(np_num_add) # out: [ 5  7  9 11 13]

このように,配列の要素内の演算をnumpy.arrayでは簡単にやることができます.

いいとこ

  • 配列の要素同士の演算を簡単にできる
  • 多次元でも扱いやすい
  • 計算が早い(ことが多い)

わるいとこ

  • 慣れないと扱いにくい
  • 1つ1つの長さが違う多次元配列には向かない
  • 数値以外は使いづらい

所感

数学的なことをやるなら絶対numpyです.
scipyという応用的な計算(積分とか)ができるライブラリとも相性がいいです.
計算をいっぱいやるときに慣れてきたら,使ってみるといいと思います.
listとの違いを把握すれば,使いやすいです.

numpy.matrix

  • 使ったことない
  • 色々調べましたが,ndarray使えばいいみたいです
  • m×nの行列を使う時は便利みたい
  • 以上.

まとめ

それぞれを一言でまとめるとこうなります.

list: 基本
dict: 名前と値の結びつきが強い
set: 被りなしのlist
tuple: 書き換え不可のlist
numpy.array/numpy.ndarray: 数値計算特化list
numpy.matrix:numpy.ndarrayとこんがらがるから使わない

フローチャート

個人的にどれを使うかを決める時のなんとなくのフローを図で示すとこうなります.
flowchart.png

実際はもう少し複雑ですが,慣れるまではこんな感じで考えるといいかと思います.
tupleは使わないので入ってません.
(本当はsetもあまり使わない)

終わりに

今回はPythonの配列っぽいやつらを比べました.
細かいところやライブラリを含めるともっとあります.
しかし,この辺が基本となるので,ここが理解できれば,他の理解も捗るかと思います.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[with構文の理解・応用] Pythonの標準出力の出力先を柔軟に切り替える

Pythonの標準出力(printやsys.stdout)の出力先(file出力, console出力)をwith構文で適宜切り替えながら使う方法を紹介します。

動機

以下の様にすると標準出力の出力先を処理に応じて変更できますが、

import sys
# 一時的にfile出力に変更
sys.stdout = open('out.log', 'a+')

...
sys.stdout.write('fugahoge')
...

# console出力に戻す
sys.stdout = sys.__stdout__

処理が終わった際に、元に戻す処理が手間なので何かないかなーと考えていて、with構文を使うとfile open()後のclose()が不必要になったことを思い出して、本記事を書くに至りました。

本記事に書いてあること

  • 標準出力の変更法
  • with構文の説明
  • with構文での標準出力の出力先の変更法

with構文

with構文は、以下の様なファイルの読み書きやtensorflowのgradient_tapeなど色々な場面で使われています。

with open('', 'r') as f:
  f.read()

参考: with構文(Python)

with構文では何が起きているか

with構文を使うと何が起きているかというと、withの後にinstanseを指定(もしくは生成)すると、以下のような流れで処理が走ります。
1. そのinstanseの.__enter__()メソッドが呼ばれる
2. with構文内の処理がはしる
3. そのinstanseの.__exit__()メソッドが呼ばれる

コード例

以下のようなコードを実行すると、

class Logger():
    def setIO(self, *args, **kwargs):
        # TestIO instanseの生成
        return TestIO(*args, **kwargs)

class TestIO():
    def __enter__(self):
        print('enter')

    def __exit__(self, *args):
        print('exit')

logger = Logger()
with logger.setIO():
    print('---- in with syntax ----')

以下のように出力されます。

console
enter
---- in with syntax ----
exit

これで、.__enter__()メソッド -> with構文内の処理 -> .__exit__()メソッド、の順に処理がはしっていることが確認できました。

参考:
- with構文とは何なのか - 年中アイス
- [Python] with構文で使用できるクラスを実装する

標準出力の出力先をwith構文で柔軟に切り替える

最後に、本記事の主題である「標準出力の出力先をwith構文で柔軟に切り替える」のコード例を紹介します。

import sys

class SetIO():
    """with構文でI/Oを切り替えるためのクラス"""
    def __init__(self, filename: str):
        self.filename = filename

    def __enter__(self):
        sys.stdout = _STDLogger(out_file=self.filename)

    def __exit__(self, *args):
        sys.stdout = sys.__stdout__

class _STDLogger():
    """カスタムI/O"""
    def __init__(self, out_file='out.log'):
        self.log = open(out_file, "a+")

    def write(self, message):
        self.log.write(message)

    def flush(self):
        # this flush method is needed for python 3 compatibility.
        pass

print('before with block')

with SetIO('out.log'):
    # file出力に切り替え
    print('---- in with syntax ----')

print('after with block')

上記のコードを実行すると、
コンソールには、以下が出力されます。

console
before with block
after with block

そして、ファイル (out.log)には以下が出力されます。

out.log
---- in with syntax ----

上記の方法で標準出力先をwith構文を切り替えることができました。

まとめ

出力先の変更は、組み込みのlogger moduleの名前空間を使えば可能ですが、細かく出力先をかえるには不便だと思っています。(loggerに精通していないだけの可能性はあります)
そこで、本記事で紹介した手法が使える場面もあると考えています。
何かの参考になれば幸いです!

Refs

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python(Bottle)で爆速!WebAPI開発

概要

とあるWebアプリ開発時に、別サーバーのWebAPIを呼ぶ必要が出てきました。
開発時には擬似環境が必要となるとのことで、簡単なWebAPIを爆速で構築したメモです。

環境

  • 言語
    Python 3.7.5
    理由:Windowsのexeインストーラーがあったから

  • Webフレームワーク
    Django: 大規模向け、機能豊富
    Flask: 中小規模向け、そこそこの機能、WSGI準拠
    Bottle: 小規模向け、軽量、WSGI準拠
    上記の特性から今回はスピード感ありそうなBottleを採用しました。

  • サーバー
    Windows Server
    Apache 2.4.37 VC15

Pythonのインストール

下記サイトからインストーラーをダウンロードし、インストーラーを起動
https://www.python.jp/install/windows/install_py3.html

インストール時のオプションで「全てのユーザーにインストール」と「環境変数へパスを追加」みたいなのにチェック入れる。
(チェックを入れないと、OSのログオンユーザーのフォルダにインストールされ、環境変数も自分で設定しないといけません。)

インストーラーが完了したら、コマンドプロンプトで

python --version

バージョンが出てくればインストール完了。

必要なパッケージのインストール

続いて、コマンドプロンプト(管理者権限で実行)で下記コマンドたちを実行する。

Bottleのインストール

pip install bottle

DBはPostgreSQLを使用しました。
DB接続にはpycopg2を使用します。

pip install psycopg2

Hello Worldする

動作することを確認します。
index.pyを作成し、以下の通り編集します。

index.py
from bottle import route, run, template 

@route('/hello/<name>')
def index(name):
  return template('<b>Hello {{name}} </b>!', name=name)

run(host='localhost', port=8080)

これだけです・・・やばくないですか・・・

コマンドプロンプトでpython index.pyを実行するとlocalhostで起動します。
そしてブラウザで http://localhost:8080/hello/tsumasakky にアクセスすると
画面にHello tsumasakky!と表示され、成功です。

Webサーバー構築

Apacheのインストール

https://www.apachelounge.com/download/VC15/
このあたりからダウンロード。

解凍してでてきた"Apache24"を、サーバーのC直下にコピー

WSGIのセットアップ

以下のサイトからファイルをダウンロード
https://www.lfd.uci.edu/~gohlke/pythonlibs/#mod_wsgi

ファイル名ですが、

Apache 2.4 VC "15"
Python"37"
Windows "64"bit

なので、「mod_wsgi-4.6.8+ap24vc15-cp37-cp37m-win_amd64.whl」
をダウンロードしました。環境に合わせてダウンロードしてください。

コマンドプロンプトでインストールします。(ダウンロードしたファイルの場所を指定)

pip insatll mod_wsgi-4.6.8+ap24vc15-cp37-cp37m-win_amd64.whl

httpd.confの編集

まず、mod_wsgiの設定を取得するため、コマンドプロンプトで以下のコマンドを実行。

mod_wsgi-express module-config

すると、以下のように表示されます。

LoadFile "c:/program files/python37/python37.dll"  
LoadModule wsgi_module "c:/program files/python37/lib/site-packages/mod_wsgi/server/mod_wsgi.cp37-win_amd64.pyd"  
WSGIPythonHome "c:/program files/python37"  

表示された設定をコピーし、Apacheのhttpd.confに追記する。(Load Moduleの最後くらいに)
さらに、このあと作成するアダプターへのルーティングを追記する。
config:httpd.conf
WSGIScriptAlias /api C:\Apache24\htdocs\adapter.wsgi

※ユーザー固有のフォルダとかに配置するとアクセス権限なくて403エラーになったり。

アダプターの作成

wsgiの設定がうまくいっているかどうかを確認するために、WSGIScriptAliasで設定したパスにadapter.wsgiファイルを作成します。

adapter.wsgi
def application(environ,start_response):
    status = '200 OK'
    output = b'Hello World!'
    response_headers = [('Content-type','text/plain'),
                        ('Content-Length',str(len(output)))]
    start_response(status,response_headers)
    return [output]

※ちなみに"output"に普通の文字列入れると500エラー
output = 'Hello World!' #Error!
→バイト文字列にする必要あり
https://stackoverflow.com/questions/34838443/typeerror-sequence-of-byte-string-values-expected-value-of-type-str-found

Apacheを再起動して、http://localhost/api にアクセス。
"Hello World"がでてきたら成功です!

アダプター経由で自前のアプリを起動

先ほどのindex.pyadapter.wsgiを編集します。

adapter.wsgi
import sys, os
dirpath = os.path.dirname(os.path.abspath(__file__))
sys.path.append(dirpath)
os.chdir(dirpath)
import bottle
import index
application = bottle.default_app()
index.py
from bottle import route, run, template
from bottle import TEMPLATE_PATH
import psycopg2

@route('/hello')
def hello():
    return template('<b>Hello World!</b>',)

@route('/users')
def users():
    conn = psycopg2.connect("postgresql://postgres:postgres@localhost:5432/sample")
    cur = conn.cursor()
    cur.execute('select * from sample.users;')
    users = cur.fetchall()
    cur.close()
    conn.close()
    return template('<b>{{users}}</b>!', users=users)

if __name__ == '__main__':
    run(host='localhost', port=8080, debug=True, reloader=True)

Apacheを再起動し、http://localhost/api/users にアクセスすると、DBからユーザーの一覧が取得できました。

感想

サーバーの構築には若干手間取ったものの、実際のアプリコーディングは10分程度でできました!
超爆速です!

ちなみにこのあとはpycopg2でDBから辞書型でデータ取得し、dict -> jsonに変換して返す~
みたいな対応でAPIを構築しました。

上記のやり方でできない、またはこんな方法もある!という方はぜひコメントくださいませ!

最後までお読みいただきありがとうございました。(m´・ω・`)m

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スクレイピングする際の自分なりのベストプラクティス

これはPython Advent Calendar 2019の13日目の記事です。

Python要素薄めになっちゃったけど、スクレイピングの文脈でPythonよく出てくる気がするから許してください

概要

  • (自分なりの)スクレイピングシステム構築方法のベストプラクティス的なものがちょっとだけ定まった
  • 公開していろいろ意見を聞いてみたい
  • たぶんウェブデベロッパーがスクレピングやるときに知っておくとちょっとだけ幸せになるかもしれない
  • 誰かの役に立てればいいなあ(希望)

構築方法の重要な点

重要な点は以下の2点

  1. DOMにアクセスしてデータを取得するスクレイピングする処理部分はJavaScriptを使う(とくにウェブデベロッパーの方は)
  2. Chrome, ChromeDrive, Seleniumとかのスクレピング環境にはDockerとかを使う

構成図は以下のような感じです。(OSとか書いてないけど、だいたいのイメージ)
Pythonの部分は任意でSeleniumを叩ければなんでもいいです。(以下のPythonはSeleniumを使うモノとして読み替えてください)

Group 5.png

JavaScriptを使ってスクレイピング処理

スクレピング関係のシステムを作るときにめんどくさいのはライブラリ選定だったりします。

個人的にPythonは好きだったりするので、昔はPyQueryとかBeautifulSoupを触っていました。

しかし、結構かなり面倒くさいです。

どういう風にセレクタを書けばいいのかググって実行して、ミスって、修正して、...みたいな感じなります。

最終的な結論が「Chrome Developer ToolsでJSのセレクタを書いて、欲しい情報を取得するJSを書いて、それをSeleniumから実行させる」です。(これが一番楽だと思います。)

(おそらくjQueryを触ったことがある人なら一番とっつきやすいはず)

以下のようなかんじ。

res = driver.execute_script(driver, """
    return (() => {
        // 以下をChrome Developmer Toolsでいろいろ試して作成する
        let tmp = [];
        $(".sample-area li a").each( (idx, element) => tmp.push($(element).attr("href")) )
        return tmp
    })()
""")

Chrome Developmer Toolsは以下のようなやつ。F12とか押したらでます。(Consoleタブを開くとJSがページ内で実行できます)
簡単な話、クラス情報とか丸コピして$(".class").text()とかやるだけでデータとってこれたりします。

Screen Shot 2019-12-13 at 18.39.30.png

これをライブラリの特有の関数でいろいろやろうと思うとめんどくさいのはなんとなくわかるとおもいます。

また、スクリプトがJSなのでスクレイピングライブラリに依存せずに、移植も楽です。

Chrome, ChromeDrive, Seleniumとかのスクレピング環境にはDockerとかを使う

ChromeDriverの設定とかChromeとのバージョンの相性とかいろいろやるのは人生の無駄遣いです。
Dockerとか環境をまるっともってこれるものを使いましょう。

単純なHTML,CSSのみで構成されているサイトならcurlとかで引っ張ってきてやるのもいいと思います。(単純なウェブサイトなら←ここ重要)
サーバーサイドでレンダリングされていても、内部のJSでわりとなんかゴリゴリいじってるサイトは多いです。

Python + Chrome + ChromeDrive + Seleniumの構成のサンプルを作ってみたのでスターをつけてもらうと喜びます。↓
https://github.com/redshoga/python-selenium-container

実装の流れ

  1. スクレピングで取得したいデータ、場所を明確にする。
  2. Chrome Developer Toolsでその取得したいデータが取得できるスクリプトを作成。
  3. Seleniumで作ったスクリプトを動かしてデータを煮るなり焼くなりする。

おわり

マサカリダニゲロー>???  ??

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

trigramで自分のツイートからランダム文を生成する

記録です。説明軽くしか書いてないです。

やり方

準備

自分のツイート履歴をダウンロードします。Twitterページで「設定とプライバシー」→「アカウント」→「Twitterデータ」→「Twitterデータをダウンロード」でリクエストして、しばらくしてメールアドレスにダウンロードリンクが届くのでそこからダウンロードします。
2019年の夏以降ダウンロードデータの仕様が変わってtweets.csvtweets.jsになってしまったそうなので、面倒なので他の方が書いてくれたツールを利用してtweets.csvを作ります。(https://17number.github.io/tweet-js-loader/

作業場と同じディレクトリにtextフォルダを作ってその中にtweets.csvを放り込んで準備完了。

次にtweet.pyの中身について。
まずは以下の部分でtweets.txtを作ります。tweets.csvからツイート本文を抜き出してtxtファイルにしています。

tweet.py
import csv
import re

rawfile = "text/tweets.csv"
infile = "text/tweets.txt"
outfile = "text/tweets_wakati.txt"


with open(rawfile,'r') as f:
    reader = csv.reader(f)

    with open(infile,'w') as f:
        for d in reader:
            if len(d) > 2:
                f.write(d[2])
            f.write('\n')

次にjanomeを利用して単語を分ち書きにして、モデルに学習させます。janomeが入ってない人はpip install janomeを先にしてください。
ちなみに、アルファベットや特定の記号、質問箱などを排除してなるべく日本語で意味が通る文ができるようにしています。

tweet.py
from janome.tokenizer import Tokenizer
t = Tokenizer()


with open(infile,'r') as f:
    data = f.readlines()

p = re.compile('[a-z]+')
p2 = re.compile('[:/.@#質問●]+')

with open(outfile,'w') as f:
    for i in range(len(data)):
        line = data[i]
        if p2.search(line):
            pass
        else:
            for token in t.tokenize(line):
                if p.search(str(token.surface)):
                    pass
                else:
                    f.write(str(token.surface))
                    f.write(' ')
            f.write('\n')



words = []
for l in open(outfile, 'r', encoding='utf-8').readlines():
    if len(l) > 1:
        words.append(('<BOP> <BOP> ' + l + ' <EOP>').split())


from nltk.lm import Vocabulary
from nltk.lm.models import MLE
from nltk.util import ngrams

vocab = Vocabulary([item for sublist in words for item in sublist])

print('Vocabulary size: ' + str(len(vocab)))

text_trigrams = [ngrams(word, 3) for word in words]

n = 3
lm = MLE(order = n, vocabulary = vocab)
lm.fit(text_trigrams)


最後にランダム文生成です。

tweets.py
for j in range(10):
    # context = ['<BOP>']
    context = ['<BOP>','<BOP>']
    sentence = ''
    for i in range(0, 100):
        # contextのうち最後の2単語から次に繋がる確率0じゃない単語をランダムに選ぶ
        w = lm.generate(text_seed=context)

        if '<EOP>' == w or '\n' == w:
            break

        context.append(w)
        sentence += w



    print(sentence+'\n')

ランダムに10個の文が出力されます。
コピペするときは上に書いたコードを一つのファイルにまとめるか、jupyter notebookでセルに分けて実行するかなどしてください。
モデルの学習に少し時間がかかる場合があるので、後者の方法がオススメです。

結果

 2019-12-13 19.00.15.png

上みたいな感じで10個の文が出力されるはずです。
結構面白いので無限に試せちゃいます。皆さんも是非やってみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

processをkillした日(flaskでAddress already in useというerrorが出た)

起動しようとするとerrorが出た

OSError: [Errno 98] Address already in use



process を探して

ps -fA | grep python

任意のものを葬る

kill xxxx(任意)

参考

http://yusuke-ujitoko.hatenablog.com/entry/2018/07/14/144227
https://qiita.com/shuntaro_tamura/items/4016868bda604baeac3c

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでつくる彼女入門 ~Tinder自動化プロジェクト~ 第6話

目次

やったこと 主な出来事
第1話 自動右スワイプ
第2話 自動メッセージ送信 女性とマッチした
第3話 ライブラリ化 マッチした女性とLINEを交換した
第3.5話 アクセストークンの再取得 これまでのコードではトークンが取得できなくなっていた
第4話 データ収集 LINEの返信が来なくなった
第5話 データ分析 プロフィール文編 仲良くなった人から情報商材を勧められた
第6話 データ分析 画像編 リアルの知り合い女子から最近やたらと深夜に電話がかかってくる(?)

コードはGitHubから閲覧できます。

前回までのあらすじ

  • TinderのAPIを簡単に叩くためのライブラリを作成した
  • マッチする相手を探すべく、データの収集を行った
  • プロフィール文の分析を行った

近況

最近寝不足です。
また1人、知り合いに彼女ができたようです。この、裏切り者が...。
そういえば、先日GPUを入手したので、今回からGPUを利用して学習を行っています。

先行研究の話

Tinderのスワイプ戦略になんらかのモデルを噛ませることを考えたのは、僕が初めてではありません。
僕が調べただけでも、顔写真を載せている人だけをスワイプしている人[1]や自分の好みの写真をDNNに学習させてスワイプしている人[2][3]、顔に加工がされているかどうかを判定している人[4]などがみつかりました。

...さて、我々は何がしたいのでしたっけ。

機械学習とは厄介なもので、動くとそれなりに楽しいので、気がつくとドンドンと目的が当初の目標からずれていきます。改めて原点に立ち返ると、我々は彼女が欲しくてコードを書き始めたのでした。
世の中に出てくる情報の大半は、「不要な人とマッチしない」ためのコードですが、我々が必要としているのは「なるべく多くの人とマッチする」ためのコードです。なにせ、現状では1日あたり1人とマッチするかどうかという状況なので、好みでない人とマッチした場合は手動で解除すればいいのです1(大量のマッチが発生して手動での解除が追いつかず困っている人にとっては、もちろんこの限りではありません)。自分から出会いの幅を狭めてどうする2

彼女を作るための努力という意味では、[5]が参考になります。こちらは、今我々が挑戦しているようにマッチしそうな人を探すのではなく、「なるべく多くの人にLikeを貰えるプロフィールを作成する」というアプローチで出会いを試みています。使うマッチングサービスが異なればユーザーの評価戦略も異なりうるので単純適用はできませんが、面白い試みだと思います。いつかTinderでもやってみたいのですが、その場合は自己紹介文を変えたプロフィールを複数用意してA/Bテスト3や、そのスコアを基に強化学習を行うことになるのでしょうか。十分なデータの収集のためには長い時間と大量の電話番号が必要なことが予想され、正直面倒くさいので、この方針は自分の中では保留になっています。
なお、[5]によると、「学歴」「子供がほしいか」「社交性」「お酒」に関する適切な記述を自分のプロフィールに含めると女性から右スワイプされやすくなるようです4

画像認識

上に長々と余計なことを書いてしまいましたが、要は

  • In: プロフィール写真
  • Out: マッチするかどうか

という機械学習機を作りたいという話です。

モデル構築

画像認識といえば、ということで、CNNを用いてプロフィール画像からマッチするかどうかを推定します。
まずはdataフォルダーから画像を読み込みます。

analytics.py
import pandas as pd
import cv2
import numpy as np
from tqdm.notebook import tqdm
import os
import re

filePath = "data/tinder.xlsx"
imagePath = "data/photos"

df = pd.read_excel(filePath)
df.drop_duplicates(inplace=True, subset="id")
df.set_index("id", inplace=True)

X=[]
y=[]
for fileName in tqdm(os.listdir(imagePath)):
    try:
        id_ = re.match("([a-z0-9]*)-\d( \(\d\))?.jpg",fileName).group(1)
        match = df.loc[id_]["match"]
        filePath = os.path.join(imagePath, fileName)
        img = cv2.imread(filePath)
        img = cv2.resize(img, (120,120))
        X.append(img)
        y.append(match)
    except:
        pass
X=np.asarray(X)
y=np.asarray(y)

画像のサイズは120*120で統一しました。
読み込んだ画像を255で割って0-1の範囲に収め、trainとtestに分割します。

analytics.py
from sklearn.model_selection import train_test_split

X = X/255
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=8888)

データの準備ができたので、CNNを構築します。今回は、2層の畳み込み層+2層の全結合層のモデルを用意しました。

analytics.py
import keras
from keras.models import Sequential
from keras.layers import Conv2D, Dense, ReLU, Dropout, Flatten, MaxPool2D

def getModel():
    model=Sequential()
    model.add(Conv2D(3,3,input_shape=(120,120,3)))
    model.add(ReLU())
    model.add(MaxPool2D((2,2)))
    model.add(Dropout(0.25))
    model.add(Conv2D(3,3,padding="same"))
    model.add(ReLU())
    model.add(MaxPool2D((2,2)))
    model.add(Dropout(0.25))
    model.add(Flatten())
    model.add(Dense(1024))
    model.add(Dense(2,activation="softmax"))
    return model

訓練及び予測を行います。

analytics.py
model = getModel()
model.compile(optimizer=Adam(), loss='categorical_crossentropy', metrics=["accuracy"])
model.fit(X_train, to_categorical(y_train), epochs=30, validation_data=(X_test, to_categorical(y_test)))

y_pred = model.predict(X_test)
y_pred = np.exp(y_pred)
y_pred = (y_pred/np.sum(y_pred, axis=1).reshape(-1,1))[:,1]
print(roc_auc_score(y_test, y_pred))
#>>0.5116927510028815

auc0.51...ランダムよりはマシと言ったところでしょうか。
あまり使い物になりません。マッチしたデータ少ないしな...

そこで、転移学習を試してみたいと思います。
転移学習では、事前になんらかのタスクを学習させたモデルを、他のタスクに適用します。CNNの画像認識モデルにおいては、畳み込み層で画像の普遍的な特徴が抽出されるらしいことが経験的に知られており、最後の全結合層を適切に作り直すだけで別のタスクを行うモデルが出来上がります[6]。

image.png
CS231n: Convolutional Neural Networks for Visual Recognition Lecture 7より

今回はVGG16をもとに、全結合層を追加してマッチ確率を出力させてみます。

analytics.py
#データの準備は以前と同じで、X_train, y_train, X_test, y_testはすでに準備されているものとします。
from keras.applications.vgg16 import VGG16

def getModel():
    model = VGG16(weights="imagenet", include_top=False)
    x = model.output
    x = GlobalAveragePooling2D()(x)
    predictions = Dense(1, activation="linear")(x)
    model = Model(inputs=model.input, outputs=predictions)
    for layer in model.layers[:-3]:
        layer.trainable=False
    return model

model = getModel()
model.compile(optimizer=Adam(), loss="mse", metrics=["mse"])
model.fit(X_train, y_train, epochs=10, validation_data=(X_test, y_test))
y_pred = model.predict(X_test)
print(roc_auc_score(y_test, y_pred))
#>>0.6025131864722308

無事aucが0.6を超えてくれました。ようやく使えるモデルが完成したのではないでしょうか。
次回は文章と画像、それからテーブルデータをもとに、いよいよマッチする人を探し出します。クリスマス前には完成させたいなぁ。

待ってろよ!未来の僕の彼女!!

参考文献

[1]https://note.mu/sarasara201512/n/n20ec9765a387
[2]https://qiita.com/KR_bangkok/items/00b5ed45f5a8c1428960
[3]https://github.com/joelbarmettlerUZH/auto-tinder
[4]https://blog.aidemy.net/entry/2018/07/05/172157
[5]https://qiita.com/data_psyence/items/54bab846337fe1ca61e4
[6]https://qiita.com/ANNEX_IBS/items/55c7a8984fe88a756965


  1. この理由から、ビジネス勧誘等を行う邪魔なアカウントを機械的に排除することも現時点では考えていません。わざわざモデルを作らなくても、実際に会話してみればすぐに分かることなので。 

  2. もちろん、顔の有無などを特徴量として使うことはできます。 

  3. 先日Googleの人と仲良くなったのですが、その人の同僚が京大出身で、自己紹介の学歴欄を埋める/埋めないでA/Bテストを行ったそうです。結果としては、「大学名(京都大学)を記入した場合としなかった場合では、どちらも0人の女性とマッチし、有意な差は見られなかった」とのことでした。あまりにも悲しい結末で、それ以上の詳細は聞けませんでした。 

  4. 「子供がほしいか」という質問項目がデフォルトで存在するから機能しているのであって、自由記述であるTinderにおいてこんな内容をわざわざ書くと敬遠される気もします。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SARIMAモデルとProphetモデルの時系列データ予測の比較

こんにちは。
未来検索ブラジルの菅藤です。

今日は何番煎じかわからないけど時系列データの予測について書いてみたいと思います。

1. はじめに

 時系列データの予測って基本的にそんなに使えないイメージがあるのですが、どれほどのものか、実践で使えそうかなどをみていきたいと思います。

具体的にやってみたのは以下の通り

Twitterのフォロワー数を日々取得する
あの渋い APIで頑張る

自分のTwitterのフォロワー数の予測を立ててみる
(1) SARIMA modelで予測
・[Combining neural network model with seasonal time series ARIMA model]
https://www.sciencedirect.com/science/article/pii/S004016250000113X
・[SARIMAで時系列データの分析(PV数の予測)]
https://www.kumilog.net/entry/sarima-pv @xkumiyu

(2)Prophet modelで予測
・[Prophet公式]
https://facebook.github.io/prophet/docs/quick_start.html
・[時系列解析ライブラリProphet 公式ドキュメント翻訳1(概要&特徴編)]
https://qiita.com/japanesebonobo/items/96868e58d4da42d36807 @japanesebonobo

今回の内容

ツイートもせず、日々減っていくフォロワー数の予測は僕の心をさらに抉ってくれる。
先に結論を言うと、フォロワー数は減るし、増える目処は立たない。

2. 環境

  • マシン
    • Mac --version 10.15.1  
  • Python
    • Python3 --version 3.7.0

3.下準備

日々のフォロワー数のデータはこんな感じ。
見るに耐えない。(https://twitter.com/Ndtn_/)
http://web.sfc.wide.ad.jp/~nadechin/follower.csv

date        follower
2018/9/6    39.569
2018/9/7    39.57
2018/9/8    39.573
   .           .
   .           .
   .           .
2019/12/10  37.861

4. 時系列データの処理

学習データとテストデータを分ける。
pandasでもnumpyでも何でも良いがひとまず用意するのは、
・2018/09/06 ~ 2019/12/10 の元データ
・2018/09/06 ~ 2019/11/30 の学習データ
・2019/12/01 ~ 2019/12/10 のテストデータ

ADF検定でデータの定常性の確認をする。
・[statsmodels.tsa.stattools.adfuller]
http://www.statsmodels.org/dev/generated/statsmodels.tsa.stattools.adfuller.html
・[帰無仮説, 有意水準]
http://www.gen-info.osaka-u.ac.jp/MEPHAS/express/express11.html

res = sm.tsa.stattools.adfuller(df.follower)

出力結果は以下の通り
p-value = 0.9774
⇨p-value > 0.05

したがって定常性を持つとはいえない。
定常性を持たせるために、差分を取り季節性を取り除く。

predict.py
data = [Scatter(x=df.index, y=df.follower.diff())]

続いて季節性の除去。

predict.py
data = [Scatter(x=df.index, y=df.follower-res.seasonal)]

これで再びADF検定を行う。

p-value = 1.109e-25
⇨p-value < 0.05

結果、定常性を持つ時系列データへと処理を行うことができた。

5. 時系列データの予測

SARIMA modelの場合、各々のデータのモデルの作成は

predict.py
# coding:utf-8
from statsmodels.tsa.statespace.sarimax import SARIMAX

model = SARIMAX(
    train,
    order=(p, d, q),
    seasonal_order=(sa, sd, sq, s),
    enforce_stationarity=False,
    enforce_invertibility=False)
result = model.fit()

で行う。
order=(p, d, q)はARIMAモデルのパラメーター
seasonal_order=(sp, sd, sq, s)は季節性のパラメーター

参照↓
・[statsmodels.tsa.statespace.sarimax.SARIMAX]
https://www.statsmodels.org/dev/generated/statsmodels.tsa.statespace.sarimax.SARIMAX.html
・[SARIMAで時系列データの分析(PV数の予測)]
https://www.kumilog.net/entry/sarima-pv @xkumiyu

次にProphet modelの作成を行う。

Prophetはひとまず学習データを打ち込めばよしなにモデルを構築してくれるという、
「何やってるか分からないけど予測らしい何かが出来た」を実現してくれる。
今日から俺も2秒のコピペでデータサイエンティストになれる。

predict.py
# coding:utf-8
import pandad as pd
import numpy as np
from fbprophet import Prophet

data = pd.read_csv('follower.csv')
data.follower= data.follower.apply(lambda x: int(x.replace(',', '')))
#カラム名は'ds','y'に設定しなくてはならない
data = data.rename(columns={'date': 'ds', 'follower': 'y'})
model = Prophet()
model.fit(data)

6. 時系列データの予測

・SARIMA model

SARIMA modelに掛けたテストデータの予測

2019-12-01  38002.878685
2019-12-02  38001.204647
2019-12-03  37998.080676
2019-12-04  37988.324131
2019-12-05  37981.134367
2019-12-06  37974.569498
2019-12-07  37966.333432
2019-12-08  37958.270232
2019-12-09  37956.258566
2019-12-10  37952.875398

・Prophet model

Prophet modelに掛けたテストデータの予測

2019-12-01  37958.337506
2019-12-02  37959.963661
2019-12-03  37957.304699
2019-12-04  37943.272430
2019-12-05  37934.533210
2019-12-06  37920.537811
2019-12-07  37908.529618
2019-12-08  37905.819057
2019-12-09  37907.445213
2019-12-10  37904.786251

寂しいのでplotしてみる

[全体図]
Figure_11.png

[予測部分]
Figure_12.png

[予測部分拡大図]
Figure_13.png

7. わかったこと

学習データの最終日の翌日の予測データを見てみる。

date, follower

#実データ
2019-12-01, 38003.000000

# SARIMA
2019-12-01, 38002.878685

# Prophet
2019-12-01, 37958.337506

[予測部分拡大図]を見てもらうとわかると思うけど、SARIMAデータでは学習データの翌日の予測はほぼ一致している。
学習データの次の時点の予測は向いてそう。

Prophetは正直微妙だった。

7. 1日集中で予測させてみる

2019/12/09までを学習させて2019/12/10の予測値を出して見れば意外と上手くいくのではないかと考えたのでやってみる。

以下結果
Figure_15.png

date, follower

#実データ
2019-12-01, 37861.000000

# SARIMA
2019-12-10  37868.158032

ボチボチ良い感じ。
やっぱり1日だけの予測だと実用レベルの比較的良い精度が出そう。

何度も言うけど、Prophetは正直微妙だった。

まとめ

コピペデータサイエンティストとアドベントカレンダーを遅れて投稿する奴を信じてはいけない。
あと、フォロワーは減る。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

<科目> 機械学習 第六章:アルゴリズム2(k-means)

<科目> 機械学習

目次
第一章:線形回帰モデル
第二章:非線形回帰モデル
第三章:ロジスティク回帰モデル
第四章:主成分分析
第五章:アルゴリズム1(k近傍法(kNN))
第六章:アルゴリズム2(k-means)
第七章:サポートベクターマシン

第六章:アルゴリズム2(k-means)

#https://datahexa.com/kmeans-clustering-with-wine-dataset/参考
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import cluster, preprocessing, datasets

from sklearn.cluster import KMeans

wine = datasets.load_wine()
X = wine.data
X.shape
結果
(178, 13)
y=wine.target
y.shape
結果
(178,)
wine.target_names
結果
array(['class_0', 'class_1', 'class_2'], dtype='<U7')
model = KMeans(n_clusters=3)
labels = model.fit_predict(X)
df = pd.DataFrame({'labels': labels})
type(df)
結果
pandas.core.frame.DataFrame
def species_label(theta):
    if theta == 0:
        return wine.target_names[0]
    if theta == 1:
        return wine.target_names[1]
    if theta == 2:
        return wine.target_names[2]
df['species'] = [species_label(theta) for theta in wine.target]
pd.crosstab(df['labels'], df['species'])

スクリーンショット 2019-12-13 17.46.05.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python備忘録

はじめに

未来電子テクノロジーでインターンをしているやっきーです。

まだまだ勉強中のため、間違いがあればどんどん指摘してください。

Pythonの特徴

変数宣言をしない

c言語などとは異なり、変数を定義する際に宣言をする必要がありません。変数の条件(数字以外の文字から始まる、特別な単語と被らないようにする、など)を満たしていれば宣言なしに使い始めることができます。

中かっこ { } を使わない

pythonでは、{}を使いません。for文やif文の中身はインデントで区別します。

for i in range (n): # : で区切る
    print(i*i)      #for文の中の処理
input()             #for文の外の処理 

主な記法

出力

標準出力を表示するためにはprint()を使う。

print("hello")
#hello
a = 5
print(a)
#5

ただし、数字と文字を混合して表示するときは型変換をしなければならない。

a = 5
print(a + "hands")
#エラー

print(str(a) + "hands") #数値aを文字列に変換
#5hands

入力

キーボードからの標準入力を受け付けるには、inputを使用します。

a = input()

#helloと入力

print(a)
#hello

inputを使用すると文字列が格納されるため、数字として扱いたいときは型変換をする必要があります。

関数宣言

関数を作るときは、defを用います。

def sum(a, b):
   return a+b

文字列の置換

標準入力をinput()で取得したときに、一部の文字を変えたい場合、replace()を用います。
実際の使用例は以下の通りです。

line = input()
# "co worker" と入力
line_2 = line.replace(' ', '-') #スペースを - (ハイフン)に置換
print(line_2)
# "co-worker" と出力

文字列の一部を変更する

例えば、以下のように2文字目を変更しようとしても、エラーとなり、うまく動作しません。

string = "worm"
string[1] = "a"
print(string)
#warmとしたい

#TypeError: 'str' object does not support item assignment

このような場合、解決策として2通りの方法があります。

文字列をリストに変換する

文字列(str)は中身を変更できませんが、リスト(list)は変更ができます。そのため、文字列をリストに変換して、特定の文字を変更した後、再び文字列に変換すればよいです。

string = "worm"

stringList = list(string) #リストに変換

stringList[1] = "a" #2文字目をaにする

srting2 = "".join(stringList) #リストを文字列に変換

print(string2)
#warm

文字列を分けて、間に挿入する

今回の例では、文字列の1文字目までと3文字目以降の文字の間に入れたい文字を挿入します。

string = "worm"

new_str = string[:1] + 'a' + string[2:] #stringの1文字目と3文字目以降を代入、その間にaを挿入

print(new_str) 
#warm

リストから重複を除外

一次元リストの重複を除外するにはset()を使います。
set()を用いることで重複が排除されたset型のオブジェクトとなります。リスト型として扱うためには、list(set())とすればいいです。

nums = [1, 2, 10, 1, 3, 1, 4, 2, 3]

nums2 = list(set(nums))

print(nums2)
#[1,2,10,3,4]

フレームワーク

pythonには、特定の機能をこなすためのプログラムがまとめられたフレームワークが多数存在します。フレームワークを活用することで、より効率的に開発を行うことができます。
pythonのフレームワークとして以下のようなものがあります。

  • Django
  • bottle
  • Flask
  • Tornado
  • Plone

今後は、Djangoについてさらに学習していきます。

参考URL

Solve Python | HackerRank
Pythonで、文字列の一部の文字を変更する - minus9d's diary
Pythonでリスト(配列)から重複した要素を削除・抽出 | note.nkmk.me
2019年Pythonのおすすめフレームワーク完全版!各フレームワークを徹底比較! | エンジニア案件紹介

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonスクリプトをresponderを使ってさっとCloud Runで実行できるようにする

サマリ

Cloud RunがGAされましたね。今までローカルで実行していたちょっと処理時間がかかるpythonスクリプトをお試しでCloud Runで実行したくなりました。
Cloud Runで実行するコンテナーはHTTPでリクエストを受けて実行できる必要があります。また、Cloud Runではリクエスト タイムアウトのことも考慮しないといけません。
リクエスト タイムアウトのことを考慮せずにとりあえずさっとスクリプトを動かすのにresponderが簡単だったので紹介します。

前提の話

試した環境

responder

pythonのHTTPサービスフレームワークです。ASGI(Asynchronous Server Gateway Interface)を実装したものなので、Asynchronousと名前がついているとおり非同期処理もできます。
非同期処理自体は他のフレームワークでもできます。しかし、pythonのフレームワークで有名なFlaskやDjangoだとざっと調べたところパッケージを追加でインストールしたり、設定ファイルを変えたりする必要がありました。responderのサンプルコードを見るとインストールも実装も楽そうだったので使ってみました。

やったこと

ディレクトリ構成と各ファイルについて

vaivailx@MacBook-Pro-2 responder_sample_for_cloudrun % tree -L 3 .
.
├── Dockerfile
├── requirements.txt
└── src
    ├── sample.py
    └── webapp.py

pythonの各ファイルは以下の処理をします。

  • sample.py: 実行したいメインの処理
  • webapp.py: HTTPリクエストを受け付ける処理

requirements.txtはこれだけ。

responder==2.0.4

Dockerfileではaptでパッケージの更新とrequirements.txtに書かれているパッケージをインストール。

FROM python:3.7.5-buster

ENV APP_HOME /app
WORKDIR $APP_HOME
ADD ./requirements.txt /app
ADD ./src /app

# Avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
    && apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \
    # Install pylint
    && pip --disable-pip-version-check --no-cache-dir install pylint \
    # Update Python environment based on requirements.txt
    && pip --disable-pip-version-check --no-cache-dir install -r requirements.txt \
    && rm -rf requirements.txt \
    # Clean up
    && apt-get autoremove -y \
    && apt-get clean -y \
    && rm -rf /var/lib/apt/lists/*

# Switch back to dialog for any ad-hoc use of apt-get
ENV DEBIAN_FRONTEND=

# run app
ENV PORT '8080'
CMD python3 webapp.py
EXPOSE 8080

responderを使ってHTTPリクエストを受け付ける処理。
defの前にasyncとつけるだけでその関数は非同期処理になります。

webapp.py

import logging

import responder

from sample import process_sample

logging.basicConfig(filename=f'/var/log/webapp.log', level=logging.DEBUG)

api = responder.API()

@api.route("/async")
async def asyncsample(req, resp):

    @api.background.task
    def process_param(params):
        t = int(params.get('time', 10))
        process_sample(t)

    process_param(req.params)
    resp.media = {'async success': True}

@api.route("/sync")
def syncsample(req, resp):

    t = int(req.params.get('time', 10))
    process_sample(t)
    resp.media = {'sync success': True}

if __name__ == '__main__':
    api.run()

ログ出力をしているのは、Croud Runで実行時に確認できるためです。
ルート名通り、ルートパス/asyncにアクセスすると非同期処理が実行され、ルートパス/syncにアクセスすると動機処理が実行されます。
今回はクエリパラメーターの値分スリープをするようにしました。

sample.py

import time
import logging

def process_sample(secs):
    logging.debug('process starts.')
    time.sleep(secs)
    logging.debug('process ends.')

Cloud Runでの実行結果確認

では、Cloud Runのリクエスト タイムアウトを30秒に設定して実行してみます。(5分待つのが面倒なためデフォルト値から変更しています。)

同期実行:実行時間10秒

vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.app/sync?time=10
Fri Dec 13 09:52:59 +09 2019
{"sync success": true}vaivailx@cloudshell:~ (cloudrun-test-259804)$

sync10_result.png

→正常に終了

同期実行:実行時間40秒

vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.
app/sync?time=40
Fri Dec 13 09:55:45 +09 2019
upstream request timeoutvaivailx@cloudshell:~ (cloudrun-test-259804)$

sync40_result.png

→タイムアウト

非同期実行:実行時間10秒

vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.app/async?time=10
Fri Dec 13 10:02:31 +09 2019
{"async success": true}vaivailx@cloudshell:~ (cloudrun-test-259804)$

async10_result.png


正常に終了

非同期実行:実行時間40秒

vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.app/async?time=40
Fri Dec 13 10:03:57 +09 2019
{"async success": true}vaivailx@cloudshell:~ (cloudrun-test-259804)$

async40_result.png

→正常に終了

ついでに確認したこと

ちなみにHTTPリクエスト タイムアウトは最大900秒と設定画面にありましたが、これは実行時間ではなく
あくまでリクエストを受け付けてレスポンスを返すまでの時間で、非同期にしておけば900秒以上できることは確認できました。
以下非同期で1200秒実行した結果です。

async1200_result.png

最後に

  • responderを使うと非同期処理を簡単に実装できるのを紹介しました。
  • 処理時間がかかるバッチのpythonスクリプトをCloud Runで動かしてみたいときに使うのがありかなと思います。
  • Cloud Runは、料金ページの通り、HTTPのレスポンスタイムではなく処理時間に応じて課金されます。非同期にしても処理を実行した時間はその分課金対象時間されるので注意してください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonスクリプトをresponderを使ってさっとCloud Runで実行できるようにする

サマリ

Cloud RunがGAされましたね。今までローカルで実行していたちょっと処理時間がかかるpythonスクリプトをお試しでCloud Runで実行したくなりました。
Cloud Runで実行するコンテナーはHTTPでリクエストを受けて実行できる必要があります。また、Cloud Runではリクエスト タイムアウトのことも考慮しないといけません。
リクエスト タイムアウトのことを考慮せずにとりあえずさっとスクリプトを動かすのにresponderが簡単だったので紹介します。

前提の話

試した環境

responder

pythonのHTTPサービスフレームワークです。ASGI(Asynchronous Server Gateway Interface)を実装したものなので、Asynchronousと名前がついているとおり非同期処理もできます。
非同期処理自体は他のフレームワークでもできます。しかし、pythonのフレームワークで有名なFlaskやDjangoだとざっと調べたところパッケージを追加でインストールしたり、設定ファイルを変えたりする必要がありました。responderのサンプルコードを見るとインストールも実装も楽そうだったので使ってみました。

やったこと

ディレクトリ構成と各ファイルについて

vaivailx@MacBook-Pro-2 responder_sample_for_cloudrun % tree -L 3 .
.
├── Dockerfile
├── requirements.txt
└── src
    ├── sample.py
    └── webapp.py

pythonの各ファイルは以下の処理をします。

  • sample.py: 実行したいメインの処理
  • webapp.py: HTTPリクエストを受け付ける処理

requirements.txtはこれだけ。

responder==2.0.4

Dockerfileではaptでパッケージの更新とrequirements.txtに書かれているパッケージをインストール。

FROM python:3.7.5-buster

ENV APP_HOME /app
WORKDIR $APP_HOME
ADD ./requirements.txt /app
ADD ./src /app

# Avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
    && apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \
    # Install pylint
    && pip --disable-pip-version-check --no-cache-dir install pylint \
    # Update Python environment based on requirements.txt
    && pip --disable-pip-version-check --no-cache-dir install -r requirements.txt \
    && rm -rf requirements.txt \
    # Clean up
    && apt-get autoremove -y \
    && apt-get clean -y \
    && rm -rf /var/lib/apt/lists/*

# Switch back to dialog for any ad-hoc use of apt-get
ENV DEBIAN_FRONTEND=

# run app
ENV PORT '8080'
CMD python3 webapp.py
EXPOSE 8080

responderを使ってHTTPリクエストを受け付ける処理。
defの前にasyncとつけるだけでその関数は非同期処理になります。

webapp.py

import logging

import responder

from sample import process_sample

logging.basicConfig(filename=f'/var/log/webapp.log', level=logging.DEBUG)

api = responder.API()

@api.route("/async")
async def asyncsample(req, resp):

    @api.background.task
    def process_param(params):
        t = int(params.get('time', 10))
        process_sample(t)

    process_param(req.params)
    resp.media = {'async success': True}

@api.route("/sync")
def syncsample(req, resp):

    t = int(req.params.get('time', 10))
    process_sample(t)
    resp.media = {'sync success': True}

if __name__ == '__main__':
    api.run()

ログ出力をしているのは、Croud Runで実行時に確認できるためです。
ルート名通り、ルートパス/asyncにアクセスすると非同期処理が実行され、ルートパス/syncにアクセスすると動機処理が実行されます。
今回はクエリパラメーターの値分スリープをするようにしました。

sample.py

import time
import logging

def process_sample(secs):
    logging.debug('process starts.')
    time.sleep(secs)
    logging.debug('process ends.')

Cloud Runでの実行結果確認

では、Cloud Runのリクエスト タイムアウトを30秒に設定して実行してみます。(5分待つのが面倒なためデフォルト値から変更しています。)

同期実行:実行時間10秒

vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.app/sync?time=10
Fri Dec 13 09:52:59 +09 2019
{"sync success": true}vaivailx@cloudshell:~ (cloudrun-test-259804)$

sync10_result.png

→正常に終了

同期実行:実行時間40秒

vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.
app/sync?time=40
Fri Dec 13 09:55:45 +09 2019
upstream request timeoutvaivailx@cloudshell:~ (cloudrun-test-259804)$

sync40_result.png

→タイムアウト

非同期実行:実行時間10秒

vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.app/async?time=10
Fri Dec 13 10:02:31 +09 2019
{"async success": true}vaivailx@cloudshell:~ (cloudrun-test-259804)$

async10_result.png


正常に終了

非同期実行:実行時間40秒

vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.app/async?time=40
Fri Dec 13 10:03:57 +09 2019
{"async success": true}vaivailx@cloudshell:~ (cloudrun-test-259804)$

async40_result.png

→正常に終了

ついでに確認したこと

ちなみにHTTPリクエスト タイムアウトは最大900秒と設定画面にありましたが、これは実行時間ではなく
あくまでリクエストを受け付けてレスポンスを返すまでの時間で、非同期にしておけば900秒以上できることは確認できました。
以下非同期で1200秒実行した結果です。

async1200_result.png

最後に

  • responderを使うと非同期処理を簡単に実装できるのを紹介しました。
  • 処理時間がかかるバッチのpythonスクリプトをCloud Runで動かしてみたいときに使うのがありかなと思います。
  • Cloud Runは、料金ページの通り、HTTPのレスポンスタイムではなく処理時間に応じて課金されます。非同期にしても処理を実行した時間はその分課金対象時間されるので注意してください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

<科目> 機械学習 アルゴリズム1(k近傍法(kNN))

<科目> 機械学習

目次
第一章:線形回帰モデル
第二章:非線形回帰モデル
第三章:ロジスティク回帰モデル
第四章:主成分分析
第五章:アルゴリズム1(k近傍法(kNN))
第六章:アルゴリズム2(k-means)
第七章:サポートベクターマシン

第五章:アルゴリズム1(k近傍法(kNN))

(演習5)

%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

訓練データ生成

def gen_data():
    x0 = np.random.normal(size=50).reshape(-1, 2) - 1
    x1 = np.random.normal(size=50).reshape(-1, 2) + 1.
    x_train = np.concatenate([x0, x1])
    y_train = np.concatenate([np.zeros(25), np.ones(25)]).astype(np.int)
    return x_train, y_train

X_train, ys_train = gen_data()
plt.scatter(X_train[:, 0], X_train[:, 1], c=ys_train)

スクリーンショット 2019-12-13 17.15.47.png

学習 ステップはない

予測

予測するデータ点との、距離が最も近い ? 個の、訓練データのラベルの最頻値を割り当てる

def distance(x1, x2):
    return np.sum((x1 - x2)**2, axis=1)

def knc_predict(n_neighbors, x_train, y_train, X_test):
    y_pred = np.empty(len(X_test), dtype=y_train.dtype)
    for i, x in enumerate(X_test):
        distances = distance(x, X_train)
        nearest_index = distances.argsort()[:n_neighbors]
        mode, _ = stats.mode(y_train[nearest_index])
        y_pred[i] = mode
    return y_pred

def plt_resut(x_train, y_train, y_pred):
    xx0, xx1 = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100))
    xx = np.array([xx0, xx1]).reshape(2, -1).T
    plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train)
    plt.contourf(xx0, xx1, y_pred.reshape(100, 100).astype(dtype=np.float), alpha=0.2, levels=np.linspace(0, 1, 3))
n_neighbors = 3

xx0, xx1 = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100))
X_test = np.array([xx0, xx1]).reshape(2, -1).T

y_pred = knc_predict(n_neighbors, X_train, ys_train, X_test)
plt_resut(X_train, ys_train, y_pred)

スクリーンショット 2019-12-13 17.21.24.png

numpy実装

xx0, xx1 = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100))
xx = np.array([xx0, xx1]).reshape(2, -1).T

from sklearn.neighbors import KNeighborsClassifier
knc = KNeighborsClassifier(n_neighbors=n_neighbors).fit(X_train, ys_train)
plt_resut(X_train, ys_train, knc.predict(xx))

スクリーンショット 2019-12-13 17.23.56.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python初学者が手始めに過去10年間の気象データをサクッと簡単に分析してみた。

はじめに

最近Pythonの勉強を始めました。せっかくなので、これから自分と同じようにPythonを触り始める方に役に立つ投稿をしていきたいと思います。Pythonを一度も書いたことがなかったので、今回はpythonのシンタックスの理解やどのようなライブラリーがあるのかなどを把握しながらシンプルにデータ分析を行っていきたいと思います。

参考本:
すぐに使える! 業務で実践できる! Pythonによる AI・機械学習・深層学習アプリのつくり方

手順&準備

実行環境
Google Colaboratory
- pythonライブラリーをpipインストールすることなく、ライブラリーにアクセスできる。
- 無料であり、Googleアカウントさえあれば、インストール等の作業に手間を取られることなく、すぐにコードを実行することができる

使用するライブラリー
- urllib
- panda
- matplotlib

実装

まず初めに、分析をするために必要なデータを用意します。

データの準備

気象データを分析するために、以下のURLからデータセットをダウンロードします。直接ダウンロードをしても良いのですが、urlretireveライブラリーを使ってインストールをしてみましょう。

# urllibからurlretrieveという関数にアクセス
from urllib.request import urlretrieve
# filenameという変数を用意。ファイル名をtempreture.csvにする
filename = "tempreture.csv"
# urlを指定
url = "https://raw.githubusercontent.com/kujirahand/mlearn-sample/master/tenki2006-2016/kion10y.csv"
# urlを読み込み、上の行で記述したtempreture.csvという名前のファイルとして、データを保存
urlretrieve(url, filename)

次に取得したデータを表示させてみる

Pandasは、Pythonでデータ分析を効率的に行うためのライブラリです。Pandasを使うと、データの読み込みや統計量の表示、グラフ化など、データ分析に関する作業を容易に行うことができるようになります。

# pandasをimportし使用する場合、pdと書くことが一般的らしいので、pdとします。
import pandas as pd
# 先ほど取得したcsvファイルの中身をみるために、read_csv()を使用します
pd.read_csv(filename)

実行した結果、データが4018行 x 6列あることがわかりました。

スクリーンショット 2019-12-13 16.10.33.png

気温の平均値を調べてみる

#[過去10年間のデータを辞書型にして、プログラムしやすい形にする]

history = {}
# indexと各行のデータを取得する。他の言語だとenumurate関数と同じ
for i, row in df.iterrows():
  # 月日気温を各変数に代入します
  month, day, tempreture = (int(row['月']), int(row['日']), float(row['気温']))
  # keyを「12/25」のような形にします
  key = str(month) + "/" + str(day)
  # 同じkeyが重複しないように判定を行います
  if not(key in history): history[key] = []
  # 重複しなければ、historyに追加します
  history[key] += [tempreture]

# [平均値を求める]

average = {}
# historyをループさせて、keyを取得
for key in history:
  # 計算後の平均値をkeyに紐付けてaverageに追加
  average[key] = sum(history[key]) / len(history[key])
  result = average[key]
  # print("{0}: {1}".format(key, result))

ある日にちの平均気温を調べてみる

import math

# typeチェックを行う関数(文字列以外受け付けないようにするため)
def isString(date):
  return type(date) is str

# 辞書型のaverageから指定した日付の平均値を取得する
def getTempreture(date):
  if isString(date):
    return average[date]

tempreture = getTempreture("12/25")
value = round(tempreture)
# intをstringにタイプ変換
print(str(value)+ "度です")

描画してみる

#グラフを描画するためにmatplotlibをインポートする
import matplotlib.pyplot as plt

# 月ごとに気温データを分割する処理
tempreture_per_month = df.groupby(['月'])['気温']
# 分割された気温データを月ごとに合計し、それを月ごとのデータ数で割る
average_tempreture = tempreture_per_month.sum() / tempreture_per_month.count()
# 描画する
average_tempreture.plot()

描画できました。
スクリーンショット 2019-12-13 16.31.31.png

所感

  • 初学者はGoogle Colaboratoryで始めた方が、インストールなどの作業の手間を省くことができるのでおすすめ
  • 思ったより、簡単にデータ操作や描画ができて、pythonのライブラリーのすごさを知った

初めてpythonを触ったので、まだまだわからないことが多いですが、徐々に高度な分析ができるよう学習を続けていきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む