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

Chalice で Excel 向けの CP932 CSV ファイルを作成して返す

はじめに

Webアプリケーションの管理系の仕組みを作っていると「システムのデータをExcelで開けるCSVファイルにまとめてダウンロードできるようにして欲しい」というパターンに経験上結構遭遇する。
Python3系で何も考えずにCSVを作ると UTF-8 の文字エンコーディングになるので、Excel のデフォルトの設定だと文字化けしてしまう。 もちろん、Excelでちゃんと読み込みエンコーディングを指定すれば問題なく開けるのだが、「そんなの分からんから最初から文字化けないようにして」と言われることも多い。その場合、Windows用のエンコーディング (CP932, あるいは Shift_JIS, SJIS) でCSVファイルを作成する必要がある。

今回は Chalice を使ってやっていたのだが、ドキュメントをよく見た結果ドはまりしたのでメモ。

version など

$ pipenv run chalice --version
chalice 1.21.2, python 3.8.2, linux 5.4.0-52-generic

ドキュメントを読む

今回は Response で返したいので、Responseクラスの body に適切に値を入れればどうにかなると思っていた。 しかし、記事執筆(2020/10/23)時点のドキュメントにはこうある。

class Response(body, headers=None, status_code=200)
body: The HTTP response body to send back. This value must be a string.

https://aws.github.io/chalice/api.html#response から引用・抜粋。 一部強調。

これを読んだときの思考。

「え…? CP932 にエンコードした文字列は bytes 型になるからここに渡せない…? 無理とするとファイルを作ってS3にアップロードして、これをダウンロードさせるか…」

が、body には bytes を入れても通る 。このドキュメントの stringstr型 だけではなく、より広い文字列を意図しているのだそうだ。 改めて調べてみると、同じ疑問を持った人が質問してくれていた。

実装例: 直接レスポンス

Chalice の Response#body には bytes型も渡すことができる。 それを踏まえて実装するとこんな感じになる。 ブラウザで / にアクセスすると、CSVファイルとしてダウンロードされる。

app.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

import csv
import tempfile

from chalice import Chalice, Response

app = Chalice(app_name='csvtest')


def csv_response(filename, encoding='utf8'):
    """ CSVファイルを返す方法1: 直接レスポンス """
    with tempfile.TemporaryFile(mode='r+', encoding=encoding) as fh:
        # writer で書き込み
        writer = csv.writer(fh, lineterminator='\r\n')
        writer.writerow(['ユーザー名', 'ログイン日時'])
        writer.writerow(['user01', '2000/01/01 00:00:00'])
        # 書き込んだ全てのデータを data に読み込み
        fh.seek(0)
        data = fh.read()
    headers = {}
    headers['Content-Type'] = 'text/csv'
    headers['Content-Disposition'] = f'attachment;filename="{filename}"'
    return Response(body=data, status_code=200, headers=headers)


@app.route('/')
def index():
    return csv_response('test.csv', encoding='cp932')

別解: S3へのファイルアップロード

先の例では tmpfile を使ってオンメモリにファイルを作成、さらに data を読み込んでいる。 しかし、AWS Lambdaの場合はメモリ利用量が制限された環境での実行となるので、ファイルが大きくなってくる場合はメモリ利用量にかなりの影響を与えることが想像できる。 もちろんメモリ利用量の上限を上げることで回避できる問題だが、料金が倍となると躊躇することもあるだろう。
そういった場合は AWS Lambda で提供されている /tmp 以下に一時的にファイルを作り、作成したファイルを S3 にアップロードするといった方法を用いればよい。
S3にアップロードした後は CloudFront - S3 の通信経由で作成したファイルにアクセスさせたり、一時URLを作成・提供してユーザーにダウンロードをさせることができる。

なお、 TempfileContext は AWS Lambda で用いた /tmp 以下のファイルをエラーハンドリングなど考えずに消せるように準備したものであるので、コードの本質ではない。

import os
import csv
import uuid

import boto3

s3 = boto3.client('s3')


class TempfileContext:
    """ 一時ファイルを作って消去するコンテキストを提供します """
    def __init__(self):
        tmpfile = str(uuid.uuid4())
        self.filename = f'/tmp/{tmpfile}'

    def __enter__(self):
        return self

    def __exit__(self, ex_type, ex_value, trace):
        try:
            if os.path.exists(self.filename):
                os.remove(self.filename)
        except Exception:
            pass


def create_and_upload_csv(filename, encoding='utf8'):
    """ CSVファイルを返す方法2: CSVを作って S3にアップしておく """
    with TempfileContext() as tmp:
        # 1. /tmp 領域に CSVファイルを作成
        with open(tmp.filename, 'w', encoding=encoding) as fh:
            # writer で書き込み
            writer = csv.writer(fh, lineterminator='\n')
            writer.writerow(['ユーザー名', 'ログイン日時'])
            writer.writerow(['user01', '2000/01/01 00:00:00'])

        # 2. S3 にアップロード
        s3.upload_file(tmp.filename, BUCKETNAME, f'uploads/{filename}')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Lambda Layer と CloudFormation / sam-cli で楽ちん X-Ray

はじめに

こちらの Lambda Layer と X-Ray - Qiita という記事で、X-Ray を Lambda Layer で組み込む方法が紹介されています。複数の lambda をデプロイする場合に、個別に X-Ray を組み込まなくても一発で付け外しができるので、けっこういい感じです。

けっこういい感じなんですが、すこしコマンドを叩くのが手間なので、CloudFormation 化できないかな、と思っていたところ AWS SAM CLI で Lambda Layers が ビルドできるようになったよ - Qiita という記事を見つけました。

というわけで、2つの記事の内容をガッチャンコしたら、こんなかんじで

  X-Ray の LambdaLayer を CloudFormation で作れて楽チン!

という記事です。

なお、 X-Ray は、AWS Lambda で使えるトレーシングツールです。X-Ray を使うことで、「Lambda のどの部分で時間がかかっているか?」「どこのリソース (DynamoDB, S3, etc) 呼び出しで時間をくっているか?」を下の図のような感じで確認できるようになります。

sample1

sample2

(図の参照元: AWS X-Ray コンソール: - AWS X-Ray

X-Ray な Lambda Layer のテンプレート定義

用意するのは、template.yaml の他には xray-layer-src/requirements.txt だけ で OK です。

template.yaml

template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: CloufFormation Template X-Ray lambda layer sample

Resources:
  # X-Ray の Lambda Layer 定義
  XRayLayer:
    Type: AWS::Serverless::LayerVersion
    Properties:
      Description: Lambda Layer for XRay
      ContentUri: xray-layer-src
      CompatibleRuntimes:
        - python3.8  # ここは利用している Python のランタイムを指定
    Metadata:
      BuildMethod: python3.8  # sam-cli でビルド時に指定が必要

  # XRayLayer を利用するラムダ定義の例
  SomeFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: lambda/src/
      Handler: lambda_handler.lambda_handler
      Runtime: python3.8
      Tracing: Active
      Layers:
        - !Ref XRayLayer  # 作成した Layer の参照

xray-layer-src/requirements.txt

xray-layer-src/requirements.txt
aws-xray-sdk

Lambda Layer に入れるライブラリの指定で、ここでは aws-xray-sdk のみを指定すれば OK です。

これで、X-Ray Lambda Layer のリソースが作成できます。デプロイは通常の sam を使うときと同じです。

sam build
sam deploy

X-Ray を利用する Lambda の例

X-Ray を利用するソースコードは、例えば次のようになります。

lambda-src/lamda_handler.py
import ...

# XRay SDK をインポート。公式ドキュメントにあるように最後にインポートする必要あり
from aws_xray_sdk.core import patch_all

# boto3 などの関連ライブラリにまとめて X-Ray のパッチをあてる
patch_all()

def lambda_handler(event, context):
    ...

参考リンク

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

tuple配列のsortは、key指定で高速化できる(Python)

はじめに

競プロ精進中に「計算量的には間に合いそうなのにTLEする...」と悩んでいたところ、前準備のsort部分がボトルネックになっていたと発覚。
ハマったポイントを共有します。

tupleの配列のsort 

key指定あり/なしの違いについて、簡潔に説明します。

key指定あり

a = [(3,3), (2,2), (3,1), (4,2)]
a.sort(key=lambda x: x[0])
print(a) # => [(2, 2), (3, 3), (3, 1), (4, 2)]

指定したkeyの大小によって、sortを行います。keyの要素が等しければ、その順序は保持されます。

sort() メソッドは安定していることが保証されています。ソートは、等しい要素の相対順序が変更されないことが保証されていれば、安定しています。(公式ドキュメント

sort(key=lambda x: (x[0],x[1])) のようにkeyをtupleにして複数指定することもできますが、
本記事では単一の要素をkey指定することを考えます。

key指定なし

何もkeyを指定しないと、
「tupleの0番目の要素で大小比較」→「同じなら1番目の要素で大小比較」→「...」
という方法でsortが行われます。

a = [(3,3), (2,2), (3,1), (4,2)]
a.sort()
print(a) # => [(2, 2), (3, 1), (3, 3), (4, 2)]

このとき内部では、tuple同士の比較演算が行われているようです。

key は一引数をとる関数を指定し、リストのそれぞれの要素から比較キーを取り出すのに使います (例えば、 key=str.lower)。それぞれの項目に対応するキーは一度計算され、ソート処理全体に使われます。デフォルトの値 None は、別のキー値を計算せず、リストの値が直接ソートされることを意味します。公式ドキュメント

「tupleの先頭の要素でsortする」だけなら、key指定ありでもなしでも目的は果たせます。
ここで「じゃあkey指定なしでいいや〜」と思ってkey指定しないと、思わぬ罠にハマる可能性があります。
tuple同士の比較は遅いのです。

実験

「tuple同士の比較」 vs. 「tupleの要素同士の比較」

この2つで、どれほどの速度差があるのかを確認します。
実行環境はAtCoderのコードテスト(PyPy3)です。
各tuple内の要素数は3です。

import random
random.seed(1)

''' n: 3通り
N = 1*(10**3)
N = 3*(10**3)
N = 5*(10**3)
'''

tl = [] # list of tuples
for i in range(N):
    l = random.randint(1,N)
    r = random.randint(1,N)
    tl.append((l,r,i)) # 3 elements in tuple

''' 
# 1. tuple同士の比較 
for i in range(N):
    for j in range(N):
        res = tl[i] < tl[j]
# 2. tupleの要素同士の比較
for i in range(N):
    for j in range(N):
        res = tl[i][0] < tl[j][0]
'''

計測結果は以下です。

N 1. tuple同士 2. tupleの要素同士
$1*10^3$ 78 ms 17 ms
$3*10^3$ 672 ms 129 ms
$5*10^3$ 1875 ms 368 ms

タプルの要素が3つなので「高々3倍程度の誤差か?」と思いきや、かなり大きな差が出ました。
怖い。

tuple配列のsort 「key指定なし」 vs. 「key指定あり」

import random
random.seed(1)

''' n: 3通り
N = 1*(10**5)
N = 3*(10**5)
N = 5*(10**5)
'''

tl = [] # list of tuples
for i in range(N):
    l = random.randint(1,N)
    r = random.randint(1,N)
    tl.append((l,r,i)) # 3 elements in tuple

'''
3. tl.sort()
4. tl.sort(key=lambda x:x[0])
'''

測定結果は以下です。

N 3. keyなし 4. keyあり
$1*10^5$ 198 ms 98 ms
$3*10^5$ 735 ms 359 ms
$5*10^5$ 1361 ms 708 ms

最初の実験ほどではありませんが、およそ2倍程度の差が出ました。

おわりに

さほど気にする差ではないかもしれませんが、問題によってはこれでハマる可能性があります。
実験のコードを見て「あの問題かな?」と思った人もいるはず。
ちなみに、key指定はlambdaではなくitemgetterのほうが高速らしいですね。

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

いろいろなサービスを使ってLINEbotを作ろう【Heroku編】

概要

LINEbotのバックエンドををさまざまなサービスを利用して作ってみようというシリーズです。
第2弾はHerokuを利用します。

そのほかのサービスを利用した構築については、以下を参照ください。

  • 第1弾:ngrok
  • 第2弾:Heroku ※本記事
  • 第3弾:Google CloudFunctions ※近日更新
  • 第4弾:Google Cloud Run
  • 第5弾:Google Kubernetes Engine
  • 第6弾:AWS Lambda (予定)

前提

全体像

Flask_Nginx_unicorn_diagramdrawio-Linebot_Heroku.png

ハンドラ実装

コード全容はこちら
main.py
import logging
import os

from flask import Flask, abort, request
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, TextSendMessage

app = Flask(__name__)
app.logger.setLevel(logging.INFO)
# 変更点1
handler = WebhookHandler(os.getenv('CHANNEL_SECRET'))
line_bot_api = LineBotApi(os.getenv('CHANNEL_ACCESS_TOKEN'))


@app.route('/callback', methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']
    body = request.get_data(as_text=True)
    app.logger.debug("Request body: " + body)
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        # 変更点3
        app.logger.warn('Invalid signature. Please check your channel access token/channel secret.')
        abort(400)

    return 'OK'


@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    # 変更点2
    if (event.reply_token == '00000000000000000000000000000000' or
            event.reply_token == 'ffffffffffffffffffffffffffffffff'):
        app.logger.info('Verify Event Received')
        return
    # オウム返し
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text))


if __name__ == "__main__":
    app.logger.setLevel(logging.DEBUG)
    app.run()

ソースコードはPython SDKのサンプルをベースに以下の修正を加えています。

1. シークレットをコードに埋め込まない

チャンネルシークレットアクセストークンは秘匿情報の部類となります。仮に流出してしまった場合は、他人に自分のアカウントをのっとられてしまいます。
サンプルではチャンネルシークレットアクセストークンを直接コードに記述するような書き方になっていますが、この状態でだれもが見られるリモートリポジトリにコードをpushすることはできませんね。(プライベートリポジトリであれば多少話は別ですが)
そこで、実行時のサーバ・コンテナの特定の環境変数に値を設定し、コード側はその環境変数から値を読み込むように記載します。(万全ではないですが、対応前に比べれば段違いにセキュアなつくりとなっています)

- handler = WebhookHandler('YOUR_CHANNEL_SECRET')
- line_bot_api = LineBotApi('YOUR_CHANNEL_ACCESS_TOKEN')
+ handler = WebhookHandler(os.getenv('CHANNEL_SECRET'))
+ line_bot_api = LineBotApi(os.getenv('CHANNEL_ACCESS_TOKEN'))

2. LINE DevelopersからのVerifyを成功させる

本対応は必須ではありません。
LINE DevelopersのWebhook URLを設定する画面にVerifyボタンが存在し、ここから設定したバックエンドへの疎通を取るような形になっています。
しかし、サンプルをそのまま利用すると、このVerifyのレスポンスコードが200になることはありません。
これはVerifyリクエストのreply_token00000000000000000000000000000000またはffffffffffffffffffffffffffffffffとなっており、どうもWebAPI側でこの2つのトークンは不正であると判別していることによるようです。
そこで、この2種類のトークンは特別扱いで200のステータスコードを返すようにhandlerへ分岐を追加します。

+ if (event.reply_token == '00000000000000000000000000000000' or
+             event.reply_token == 'ffffffffffffffffffffffffffffffff'):
+         return

3. print → logger

本対応は必須ではありませんが、推奨設定です。
ローカルでのprintデバッグというのは有用な方法ですが、通常ソースコードにprintは埋め込まない方がよいでしょう。情報を出力したい場合は通常ロギング(ライブラリ)を使用します。今回はFlaskのLoggerを利用するのが手っ取り早いと思います。

- print("Invalid signature. Please check your channel access token/channel secret.")
+ app.logger.warn('Invalid signature. Please check your channel access token/channel secret.')

Webサーバ設定ファイルの記述

上記実装で使用したFlaskフレームワークには、Webサーバとして起動することのできる機能も備わっていますが、デバッグ向けの機能となります。サービスとして公開する場合には、安定性や速度を鑑みてWSGIサーバを使用するのがよいでしょう。(このあたりの詳細はこちらも参照いただけると?)
今回はgunicornを利用します。

設定ファイル
config.py
import os

host = '0.0.0.0'
port = os.getenv('PORT', 5000)

bind = str(host) + ':' + str(port)

# Debugging
reload = True

# Logging
accesslog = '-'
loglevel = 'info'

# Proc Name
proc_name = 'Line-Bot-Practice'

# Worker Processes
workers = 1
worker_class = 'sync'

ポイントは1点のみです。

ポートはheroku側で自動設定される

herokuの仕様で、デプロイ後にポートが自動設定されるため、ポートに固定値を使用することはできません。
環境変数PORTにこの値は設定されているため、os.getenv('PORT')で取得します。

ライブラリバージョンの指定

外部ライブラリを使用する際は、利用バージョンを明示・固定するのがよいでしょう。
ローカルにインストールしている場合は、pip freeze > requirements.txtコマンドで作成することも可能です。

requirements.txt
Flask==1.1.2
line-bot-sdk==1.17.0
gunicorn==20.0.4

herokuデプロイ設定

Deploy With Git / Deploy With Docker の2パターンを選ぶことが可能です。

共通手順

どちらを選択する場合でも共通する手順です。

Terminal
# Git初期設定
git init

# ログイン / ログイン用ページへ遷移するので必要情報を入力
heroku login

# アプリケーション作成
heroku create ${APP_NAME}

# 環境変数のセット
heroku config:set CHANNEL_SECRET=${YOUR_CHANNEL_SECRET} --app ${APP_NAME}
heroku config:set CHANNEL_ACCESS_TOKEN=${YOUR_ACCESS_TOKEN} --app ${APP_NAME}

Gitデプロイ

ディレクトリ構成

ルートディレクトリを起点に処理がなされるため、すべてルートに配置します。

./
├── config.py
├── Procfile
├── requirements.txt
├── runtime.txt
└── main.py

また、2つのファイルを追加します。

ランタイムの指定ファイル

Python固有の設定です。
runtime.txtを作成することで、実行するPythonバージョンを指定することができます。
サポートされているバージョンはこちらを参照ください。

runtime.txt
python-3.8.6

起動設定ファイル

ルートディレクトリにProcfileが存在する場合は、記載されている内容でプロセスが起動されます。
ファイルの記述内容はこちらに詳細が書かれていますが、プロセス種別はほとんどのケースでwebでしょうからweb: {コマンド}と書くことが多いでしょう。

Procfile
web: gunicorn main:app -c config.py

デプロイ

herokuリポジトリのmaster(またはmain)ブランチにpushすることでアプリケーションがデプロイされます。

Terminal
git add .
git commit -m "first commit"
git push heroku master

動作確認

LINE DevelopersでWebhook URLhttps://${YOUR_HEROKU_APP}.herokuapp.com/を設定してVerify、または実際にLINEからメッセージを送信してオウム返しされてくれば問題なく稼働しています。

トラブルシューティング

  • git push heroku masterでエラー

gitのリモートブランチ設定がされていない可能性があります。
heroku git:remote --app ${APP_NAME}コマンドを実行してみてください。
設定が正しい場合は、以下のように表示されます。

  $ git remote -v
  heroku  https://git.heroku.com/mintak-heroku-linebot-practice.git (fetch)
  heroku  https://git.heroku.com/mintak-heroku-linebot-practice.git (push)
  • 送信したメッセージが返ってこない

まずはheroku logs --app ${APP_NAME}でどのようなログが出ているかを確認しましょう。
ディレクトリ構成の誤り、または環境変数が未設定の可能性が高いと思われます。

コンテナデプロイ

自身で作成したDockerfileをビルド、コンテナデプロイする方法です。

ディレクトリ構成

herokuはdocker buildを実行する際のコンテキストが、自動的にDockerfileの存在するディレクトリに固定されてしまい、外部から指定することができないため、Dockerfileはルートディレクトリに配置します。

./
├── example/
│  └── python/
│     ├── config.py
│     ├── requirements.txt
│     └── main.py
├── heroku.yml
└── herokuDockerfile

こちらも2つのファイルを追加します。

Dockerfile

ライブラリのインストールと、起動コマンドの設定を行います。
herokuアプリに設定した環境変数をよしなにコンテナに設定してくれるようです。
PYTHONDONTWRITEBYTECODEの意義については、こちらを参照ください。
なお、PYTHONUNBUFFEREDの設定を行っているDockerfileをみかけますが、Python3.7以降デフォルト設定となっているため、3.7以降であれば明示する必要性はありません。

herokuDockerfile
FROM python:3.8-alpine

ENV PYTHONDONTWRITEBYTECODE 1
COPY example/python /usr/local/application
RUN pip3 install -r /usr/local/application/requirements.txt && \
  rm -f /usr/local/application/requirements.txt

WORKDIR /usr/local/application
CMD ["gunicorn", "main:app", "-c", "config.py"]

デプロイ設定ファイル

Git側でのProcfileと同様です。
setup build release runの4ステップを設定することが可能ですが、buildステップで対象のDockerfileを指定することができていれば起動するでしょう。
ちなみにrunステップを記述すると、CMDを上書きすることができます。

heroku.yml
build:
  docker:
    web: herokuDockerfile

デプロイ/動作確認

Gitデプロイと同様です。

参考

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

視覚と聴覚を統合して学習させたぞ! な論文(原典:See, Hear, and Read: Deep Aligned Representations )

原典

See, Hear, and Read: Deep Aligned Representations
arxiv-vanity.com/papers/1706.00932/

どんな論文?

画像とキャプションのペアを学習させた。
それによって、未知の画像に対しても、定量的にいい感じの音とテキストの関係も学習できていたことがわかった。
テキスト、サウンド、イメージについてCNNを施す。
下位の層は重みを共有しないが、上位の層でのみ重みを共有するという面白さ。

どうやって学習させているのか?

ペアになっている画像と音、あるいは画像とテキストの2つについて、それらが同じグループに属するように学習をさせた(KLダイバージェンス)

どんなもの?

音声とテキストと画像データを食べさせた。学習については、画像+テキスト、画像+音声の2ペアである。しかしながら結果として音声とテキストのペアも学習することができた。(画像をブリッジとすることでペアを学習していると言っても良いよねというスタンス)

先行研究と比べてどこがすごい?

画像と音楽とテキストの3つのマルチモーダルなものにまたがる認識を機会に学習させるという点。非常に人間らしい知覚を習得する点。
これほど大規模でかつ3つの感覚・モーダルへのアプローチは初めてだと考える。
結果として音とテキストのペアは学習させていないが、画像をブリッジとしてしようすることで可能となる。
つまり 英語→あらゆる言語へ翻訳するときに、ブリッジ言語を通すみたいなやつ(鉄壁英単語的なアプローチ)
英語→画像 画像→フランス語
英語が音orテキスト。 フランス語がテキストor音
技術や手法の肝はどこ?
音声と画像とテキストに関して全てのCNNをおこなった。その上層レイヤをすべて結合する新たなネットワークを作成している点。

どうやって有効だと検証した?

クロスモーダル検索のスコアを比較した。
クロスモーダル検索とは、例えばイメージしたモーダル(テキストか画像か音)のデータが欲しいときに、別のモーダルからのクエリ検索で、目的のデータが手に入るかを試す。

メモ書き

音と言語
音声検索とか。割とよくある。
確率モデル
言語と視覚
画像→テキスト・キャプション自動生成とは違う。この実験では、画像と音声orテキストとの関係性のみを学習させている。
また、テキストに対してRNNを使わずに、CNNを使っている店も新しい。

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

クリエイティブなアートをAIで作らせてみた! 斬新をプログラミングしたぞ!(論文:Creative Adversarial Network)

元の論文

https://arxiv.org/abs/1706.07068

どんなもの?

GANとは少し違います。それは、識別器が、スタイルも学習するということ。
そして、生成器はスタイルも異なるように生成を学習することで、従来のGANよりもクリエイティブになるのではという論文。
アート論文として、アーティストは、スタイルブレイクを目指すことが多いとわかった。
アートの分布からの逸脱は最小限にしつつ、スタイルの逸脱を最大限にするように生成器を

学習

クリエイティブシステムの要件としてある学者は、1想像力、2スキル(品質)、3独自の創造物を評価する能力の3つが必要であると述べているが、この3つ全てを満たしている。
提案されたシステムの主な特徴の1つは、 アートを作成するプロセスでアートの歴史について学習することです。ただし、スタイルの概念の背後にあるアートについての意味的な理解はありません。主題、要素の明示的なモデル、芸術の原理については何も知りません。ここでの学習は、芸術への露出とスタイルの概念にのみ基づいています。その意味で、システムは新しいアートから継続的に学習する機能を備えており、学習した内容に基づいて世代を適応させることができます。

背景

昔の DE Berlyne(1924-1976)によって提案された理論に基づいています。
美学の最も重要な覚醒を高める特性は、新規性、驚き、複雑さ、あいまいさ、および不可解さであることを強調しました。
そして、一人のアーティストが作品を作り続けると、どうしても慣れてしまうから、それを避けるためにこのシステムでは頑張る。
また、刺激は強すぎても弱すぎてもでダメであるから、そこをコントロールしている。
カテゴリ(例:[ 18 ])またはキャプション(例:[ 19 ]) に基づいて画像を生成することを容易にするGANの拡張機能があります 。)。このようなラベルにトレーニングを提供することで、さまざまなアートスタイルまたはさまざまなアートジャンルの画像を生成するように設計およびトレーニングできるGANを考えることができます。

先行研究と比べてどこがすごい?

フィードバックに人間を必要としない。
googleDreamというのがあったけど、それは曖昧すぎて、抽象アートではないし、コンピュータが生成したとばれちゃう。認識不可能すぎる。適度な曖昧さを頑張る

技術や手法の肝はどこ?

識別器は、通常のGANのようにアートかアートではないかの判別も生成器にかえす。それとどのスタイルにどの程度分類できるかの値を生成器に返す。
識別器は、スタイルラベル(ルネサンス、バロック、印象派、表現主義など)に関連する多数のアートにアクセスでき、それを使用してスタイル間の区別を学習します。
生成器は、アートであるものを作り、かつ分類をできるだけ混乱させようと頑張る。

次に読むべき論文

覚醒のさまざまなメカニズムの中で、芸術にとって特に重要かつ関連性のあるものは、外部刺激パターンの特性です [ 3 ]。
マーティンデールは、アート制作システムの導出における慣れの重要性を強調しました [ 15 ]。
Wundt曲線(覚醒の度合いを図る曲線?)
Google DeepDream [ 16 ]によって生成された画像について、「ほとんどが、寮の部屋のマンダラ、またはテレンスマッケンナの本の表紙にあると思われるデジタルサイケデリアのように見えます」とコメントしました 3。他の人々は、「まばゆく、ドラッグ、そして気味が悪い」とコメントしました 4。この否定的な反応は、過度の覚醒の結果として説明される可能性があり、その結果、Wundt曲線に従って否定的な快楽がもたらされます。

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

論文:音楽の脳内処理

元の論文

http://papers.nips.cc/paper/6222-brains-on-beats.pdf

どんなもの?

画像認識などの機械学習の分野では脳を模倣をヒントに画像認識している。そのCNNという技術で行うと、画像の色や何が描かれてるか、などぱっと見わかりやすい情報を学習している層と、ぱっと見ではわかりづらい情報を学習している層(画風など)がある。すなわち分けて認識していることが明らかになっている。

この論文では、音楽を脳がどう処理するかを調べた。

結果として高レベルの処理(スタイルとか)と低レベルの処理(メロディとか)の処理は脳内でも明確に分かれていることが明らかになった。

すなわち、AIは人間を模倣して作られたものであるが、実際に人間の脳も同じように分けて物事を認識していることがわかった。(認識のやり方が複数ある)

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

論文:脳内の映像を再現する機械学習論文, (Deep image reconstruction from human brain activity)

元論文

https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1006633

どういう論文なの?自分の言葉でまとめると?

→脳内でみている映像を可視化するぞ!という論文。結果を見たら、意外とぼんやりしてるんだなぁと思いました。(もちろん精度の悪さもあると思われる)

詳しく

まず画像を見ている状態で、fMRIの反応を測定する。その測定状態において、fMRI→画像の特徴量を生成するものがある。
すなわち、本来であれば、画像→特徴量なのだが、それをfMRI→特徴量としている。この点で精度が画像→特徴量よりも低いのは致し方ない。むしろここにこそ、曖昧さが存在していて、創造性研究に役立つ可能性もあると思う。
そしてその結果から、画像の特徴量が得られる。これはおそらくCNNみたいな感じで、複数のレイヤーにおける特徴量が得られると思う。すなわち浅いレイヤーでは具体的な形を捉えており、深いレイヤーでは抽象的な形を保持しているものと考える。
これらの画像の特徴量に関する複数のレイヤーをうまく組み合わせる2つ目のネットワークに通す。
すなわち、特徴量から画像を生成するというCNNの逆を行うDGNというネットワークモデルを使っている。
結果として、アルファベットなどの幾何学的な構造はうまくいった。
しかしながら、複雑な状態の画像を想像してもらう→それを画像化 というのはものすごく難しかったとの結果。 原因としてはネットワークにもあるだろうが、おそらく人間の想像力ではそこまで詳細に描けないという点が挙げられる。

思ったこと・興味

プロ棋士の頭の中を描いてみたい。それをしてみたらどのくらい詳細に画像を描くことができるのだろうか気になる。
人それぞれ違うということもあり得るだろう。
宮崎駿も見てみたい。
fMRI→画像の特徴量 という変換ネットワーク。この時点で結構な論理的飛躍が起こっていて、精度が低いのかなぁと思った。
もちろん 画像の特徴量→画像生成 というDGNの精度も低いのだと思う。
精度が悪いフィルタを2つ通すことで、結構下がっているのは共通認識っぽいなぁ。ただその精度の悪さでも、今はアルファベットくらいなら画像生成できるのがすごいと思った。

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

論文: 頭の中で映像を描けない人(アファンタジア)についての論文,The blind mind: No sensory visual imagery in aphantasia

https://www.gwern.net/docs/psychology/2018-keogh.pdf

アファンタジアとは?

アファンタジアは低レベルの感覚的視覚イメージの欠如によって特徴づけられている。メタ認知欠如ではないことを支持する。

アファンタジアは空間イメージは平均より得意である。
空間イメージは、精神回転課題のパフォーマンスと相関があるようだ。また、関連として、幻覚症の人は、このスコア完璧。
脳の構造について、背側(初期視覚野から頭頂葉までの初期視覚野)には空間内の物体の位置に関する情報が含まれる。
腹側(初期視覚野から側頭葉までの初期視覚野)または「何を」の流れには物体の同一性に関する情報が含まれている。
2つは階層が上に行くほど複雑になる。

心の中で物体を回転させる能力は?
→補助運動野や一次運動野などの運動野に加えて、頭頂皮質(具体的には頭頂皮質)を活性化する
対照的に、参加者が静止画像を想像すると、視覚野は活動の増加を示す傾向がある
画像を想像する→視覚野から複合される。
視覚野の応答レベルが高いほど、イメージの主観的な鮮明度と相関がある。

この2つの証拠から、静的な物体イメージと精神的な回転や空間イメージに使用される神経ネットワークの分離を示唆しています。
人が風景や物体を想像するとき→視覚野だけでなく、頭頂部や前頭前野にまで及ぶ大規模なネットワークが活性化される
前頭前野→視覚野の感覚表象を活性化するフィードバック接続を駆動(アファンタジアはこれができない可能性)
視覚野と前頭前野の両方の皮質の興奮性が、イメージの強さを支配する上で重要な役割を果たしていることが示されています
以上より、アファンタジアは視覚領域、前頭前野領域、またはその両方の領域で以上な活動レベルの可能性がある。
私たちの内的世界に対する能力や経験が大きく異なる原因となっている神経学的な違いを理解するのにも役立つでしょう。

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

論文:動的な自然風景のために、ディープラーニングを使って神経から生成するよっていう論文(Neural Encoding and Decoding with Deep Learning for Dynamic Natural Vision )

どういう論文なの?ざっくり

機械学習(ニューラルネットワーク,CNN)は、人間の脳を模倣してきた。それを利用すると、そこから画像を見たときの脳内反応(MRI)を再現できるようになった!そして逆もできるようになった!つまり逆とは、MRIの反応結果から、ある画像の特徴量を予測できる!ということ。

今まで模倣と言われていたけれど、それが比喩ではなく本当にすごい精度で模倣していたんだなぁと裏付けられる論文な気がしました!

https://academic.oup.com/cercor/article/28/12/4136/4560155

アブストと自分的まとめ

畳み込みニューラルネットワークは、脳みその画像処理機構を真似しているとされたが、それがきちんとわかってきたということ。
つまり、CNNの特徴量から→fMRIを作成できるし、逆もまた然りなことが最近の研究でわかってきた!
CNNは、時系列とかを考慮してないのに学習してるのがすごい!end to end ってこういうことなのかなぁって思った。
CNN と fMRIの双方向性を補強する論文。
また、CNNはおなか側だけでなく(こっちはすでに完璧に予測している)、背面側のfMRIも上手に予測したよ!程度は低いけど。
fMRI信号を直接デコードすることで、視覚空間と意味空間の特徴表現を推定し、視覚的再構成と意味的分類を行うことができました。
自分の意見:これはあくまで、fMRIと同レベルまできたというだけの話であり、FMRIも完全にはわかっていないはず。あくまで別のやり方で、同じくらいわかるようになってきた、つまり手法Aと手法Bで矛盾が生じなかったという説明にしかならないと思われる。ただそれだけで、畳み込みニューラルネットワークすごい!とは言い切れない。その可能性もあるけど。

結果

CNNとfMRIの関係性についての結果。CNNと視覚野は、低レベルの視覚的特徴(例えば網膜トピ)と高レベルの意味的特徴(例えば顔)の類似した表現を共有しているだけでなく、抽象度の高い複数の中間レベルの視覚情報の階層的な表現も共有している(図2)
神経を再現することについての結果と妥当性。CNNの結果から、どの脳領域が活性化しているかを線形回帰モデルでやってみた。ラベルづけで分類する。

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

論文: 「赤い」+「丸い」のような単語指定だけで、いろんな画像作ってくれる論文(SCAN: Learning Hierarchical Compositional Visual Concepts

仕組みについて

画像から意味を理解する。大量のリンゴ画像→イメージへ。これにより、丸い・赤い・小さい などの特徴を獲得する。
具体化の時は、獲得した概念をもとに画像を生成する。 丸い・赤い・小さい、などによる拘束はあるが、それ以外の縛りは存在しない。したがって照明や背景について自由度はあるため、多様性のある画像が生成される。

超大事ポイント

以下のような命令によって到達することができます。"blue" AND "small"(より一般的なものからより特定のものへと階層を下降させる)、"blueeberry" IN COMMON "blueebell"(より特定のものからより一般的なものへと階層を上昇させる)、または "blueeberry" IGNORE "round"(同じく階層を上昇させる)という指示によって、概念{blue, small}に対応する新しいノードに到達することができる。

https://arxiv.org/pdf/1707.03389.pdf

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

論文: マルチリンガルの子供の学習方法を機械学習で模倣するぞ! (Visual Grounding in Video for Unsupervised Word Translation)

https://deepmind.com/research/publications/Visual-Grounding-in-Video-for-Unsupervised-Word-Translation

アブスト

視覚ベースをヒントに、多言語間の概念マッピングを行います

良いポイント3つは何か?

異なる言語の間のペアを学習させることができる。
データセットが少ない言語についても対応できる
テキストベースの翻訳技術に関しても、初期化の際に役立つことができる。

やり方

1つの料理の工程に対して、複数の言語によるナレーションをデータセットとして用いる。これにより視覚的なヒントから複数の言語を学習すると言うマルチリンガルの子供の学習方法の模倣することができる。

比較対象

ウィキペディアを用いたフランス語と英語などの多言語間のマッチング。非類似性からスコア

3つの貢献

我々の方法が、テキストベースの方法の多くの欠点に対処する既存の単語マッピング技
。教師なし学習であること。
教師なし学習
2つの言語間のコーパスなしで学習できるという点で教師なし学習である。
ビジュアルドメインZを土台として学習するぞ。

結論

YouTubeの動画から学習することができる。多様なコーパスに直面したときに、より強い効果を発揮する。

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

チームで出欠管理システムを作りました

社内チームで今年一年の成果物として出欠管理システムを作成しました!!
この記事ではその概要を紹介します。

プロジェクト概要

成果物として何を作成するかチームで話し合い、社内全体で利用でき作業効率化につながるシステムについて考えました。
社内行事の出欠管理がExcel管理されており、作業負荷がかかる状態だったため、この改善としてQR認証を利用した出欠管理システムを作ることになりました。

システム運用イメージ

  1. 管理者がユーザーのQRコードを発行
    image.png

  2. 管理者がユーザーへQRコードを送付(Slack・メール等)
    image.png

  3. 開催者がイベント開催日を登録

  4. ユーザーがイベント参加時にQRコードをかざす
    (開催者はQR認証画面を表示しておく)
    image.png

  5. 開催者がイベントの出席状況を確認する

システムの使い方

QRコード発行

  • 「QRコードを生成する」を選択します。
    01 - コピー.PNG

  • ユーザーコード・ユーザー名を入力します。
    02.PNG

  • QRコードが発行されます。
    管理者は各ユーザーへQRコードを通知します。
    03.PNG

イベント登録/QRコード読み取り

  • 「QRコードを読み取る」を選択します。
    01.PNG

  • イベント名・イベント日付を入力します。
    05.PNG

  • 登録したイベントを選択します。
    06.PNG

  • QRコード読み取り画面が表示されるので、QRコードを認証します。
    07.PNG

  • QRコードの認証に成功するとこのような画面になります。
    08.PNG

イベント履歴確認

  • 「イベント履歴を見る」を選択します。
    09.PNG

  • 登録済みのイベントが表示されます。
    10.PNG

  • イベント参加者が確認できます。
    11.PNG

活用技術

メンバーの各々が得意な分野を用いて作成されています。

  • 実行環境
    • GCP
      • Compute Engine
      • Cloud DNS
      • Cloud Load Balancing
    • Docker
      • Nginx
      • PostgreSQL
  • Webアプリ
    • JavaScript
      • node.js
      • Vue.js
      • TypeScript
  • API
    • Python
      • Flask

システム構成

GCPを利用して環境を構築しました。
Web画面、API、DBをVM上にDockerコンテナとして常駐させています。
タブレットでブラウザからURLへアクセスするとDNS・LB・Nginxを経由、Web画面またはAPIへリクエストし、QRコード生成/認証・イベント履歴閲覧などの各処理を行う実装方式としています。
QR認証履歴などの各データへのアクセスはAPIからDBへSQLを発行することで保存するようにしました。

構成図.jpg

開発期間

主担当3人で日常業務の片手間で約3か月ほどかかりました。
実際の作業時間はもっと短かったです。

苦労した点

  • 担当者3人で作った成果物の結合
    インフラ面で統一した開発環境を用意せず進めたため、実際に結合した際にCORSの考慮漏れ等でwebアプリがうまく動作しませんでした。
    躓いた点はIssueに整理して今後のチーム開発では気を付けていきます。
  • クライアントからAPIへのアクセス
    Web画面からAPIにリクエストする際、クライアントからリクエストを飛ぶことを意識していなかったため、ファイアウォールやDNS・LB・Nginxの設定に苦労しました。
    今後システム構成を考える際は意識していきたいと思います。

今後の展開

  • 管理画面へのログイン認証機能の実装
    実装工数が足りなかったため実装できませんでした。
    Amazon Cognitoを利用した実装を検討中です。
  • システム基盤をAWSへ移行
    前述のAWS Cognito実装などAWSサービスの勉強も今後チームで実施していきたいと考えています。
    併せて、基盤移行も検討中です。
  • 各社内イベントでの運用
    コロナの影響で実際に人が集まる機会がなく、テストが不十分です。
    データ量や負荷など分からない点が多いので、今後活用してもらっていきたいと考えています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【python】numpy.empty 初期値設定

numpyで初期値設定をするときにいつも numpy.zeros か numpy.ones しか使ってなかった。
この他にも初期値設定する方法 numpy.empty があったので書く。
また、初期値設定の使い方を確認する。

環境

linux
pyhton

numpy.empty

これの引数↓

numpy.empty(shape, dtype=float, order='C')

実験1
引数に何も持たせない。

>>> import numpy
>>> numpy.empty()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: empty() missing required argument 'shape' (pos 1

shapeの引数がないというエラーが出る。

実験2
shapeを持たせる。

>>> import numpy
>>> numpy.empty(shape=1)
array([0.])
>>> numpy.empty(shape=2)
array([-5.73021895e-300,  6.93125508e-310])
>>> numpy.empty(shape=3)
array([0., 0., 0.])
>>> numpy.empty(shape=4)
array([0., 0., 0., 0.])
>>> numpy.empty(shape=5)
array([4.66352184e-310, 4.03179200e-313, 4.66352172e-310, 5.54048513e+228,
       7.56680154e+168])
>>> numpy.empty(shape=6)
array([4.66352179e-310, 5.72938864e-313, 6.93125428e-310, 6.93125428e-310,
       0.00000000e+000, 1.16707455e-072])
>>> numpy.empty((2,3))
array([[4.66352179e-310, 5.72938864e-313, 6.93125428e-310],
       [6.93125428e-310, 0.00000000e+000, 1.16707455e-072]])
>>> numpy.empty(5.6)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'float' object cannot be interpreted as an integer

できた。。。このことから、shape=はなくても良い、数字に少数はできない、初期値はshapeによってすべての値が0になることもある、ということがわかった。

実験3
dtypeを持たせる。実験1からshapeは必須ということが分かるのでshapeも持たせた。

>>> import numpy
>>> numpy.empty(2,dtype=float)
array([-5.73021895e-300,  6.93125508e-310])
>>> numpy.empty(2,dtype=int)
array([-9093133594791772939,      140290164735896])
>>> numpy.empty(2,int)
array([130238442063002869,    140290164735896])

このことから、dtypeをfloatにしたら初期値が少数に、intにしたら初期値が整数になることがわかる。また、引数のdtype=の部分は省略してもよいこととが numpy.empty(2,int) からわかる。

実験4
order='C'を変える。

>>> import numpy
>>> numpy.empty(2,dtype=float,order='C')
array([-5.73021895e-300,  6.93125508e-310])
>>> numpy.empty(2,dtype=float,order='F')
array([5.73021895e-300, 6.93125508e-310])
>>> numpy.empty(2,dtype=float,order='E')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: order must be one of 'C', 'F', 'A', or 'K' (got 'E')
>>> numpy.empty(2,dtype=float,order='A')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: only 'C' or 'F' order is permitted

このことから、order='C'かorder='F'しか使えないことが分かる。また、order='C'とorder='F'の違いは見られなかった。そのため、どちらでも良いのではないかと思った。

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

Mayaのpythonでprint命令自体がエラーになる場合の対処法

以下、Windowsの特定のMayaバージョンだけに関連する話です。Macや最近のバージョンのMayaではでくわしたことがありません。


{55B3F311-97DE-4B88-944A-B52411ACC23C}.png.jpg

そんな馬鹿な!?

上記のようにprint命令自体がエラーになるいう謎現象にあった場合、システム環境変数に MAYA_NO_CONSOLE_WINDOW が設定されていないか確認しましょう。

これはコンソールウィンドウを表示しなくするために設定する環境変数です。
https://support.borndigital.co.jp/hc/ja/articles/360002474194-Maya-%E3%81%AE-Output-Window-%E3%82%92%E9%9D%9E%E8%A1%A8%E7%A4%BA%E3%81%AB%E3%81%99%E3%82%8B

MAYA_NO_CONSOLE_WINDOWが設定されているとMaya2017 Update 5など(正確な対象バージョンはわかりませんが)一部の環境でprint文が通らなくなります。自分が試した限り、設定数値が1ではなく0であってもダメでした。

printが通らない状況というのは、stdout 設定がおかしくなっているときのようで、コンソールウィンドウを表示しない事で割り当てがなくなってしまうのでしょうね。

一応、下記のコード実行でFIXできますが、他何かおかしくなってるかもしれないので、MAYA_NO_CONSOLE_WINDOW 定義をなくすほうが良さそうです。

# https://stackoverflow.com/questions/43633433/maya-python-ioerror-errno-9-bad-file-descriptor
import maya.utils as utils
 sys.stdout = utils.Output()

特定の環境だけ不具合が起こるというのがいやらしいです。


image.png

FIXED!

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

Pythonのデータフレームを可視化する (Rstudio)

前置き

はっきり言ってとても無駄なことをしてしまいました。(ぶっちゃけコードの保存用に載せます)
Rstudioでpythonを使っていくのをより便利にしたいと考え、関数を作ってみましたが、結局利便性はほとんど上がっていないです。。

Rstudioでpythonを使う方法などはこちら。

Pythonのデータフレーム

どうも。reticulate大好き人間です。

pythonのデータフレームはpandasを使うのが一般的ですが、Rstudioでpythonを使っているとデータフレームが見づらい、、という問題があります。

例えば、、

#「データサイエンティスト協会スキル定義委員」の
#「データサイエンス100本ノック(構造化データ加工編)」のデータを
# 利用させていただきます。

>>> import pandas as pd
>>> df = pd.read_csv("customer.csv")
>>> print(df)

名称未設定.png

うーん、、悪くないんだけど。。
途中省略されちゃってるしちょっとびみょいですね!

RstudioのView( )関数

解決策は簡単です。

・素直にgoogle colabを使う
(dataframeは比較的きれいに表示されます)

Rstudio特有のView( )関数を使う

View( )関数が一番手っ取り早いです。

>>> quit #pythonを抜けてRに戻る

> # library(reticulate)
> View(py$df)

こうすれば、いつも通りグリグリ動かせる超見やすいdataframeが出てきます。
名称未設定.png

関数化

ではこの

「reticulateパッケージの読み込み(読み込まないとpy$を使えません) → Viewerでの表示 → pythonに戻る」

という一連の動作を関数にしてみましょう。

py_view = function(py_df_name, i_overwrite = FALSE, repl = TRUE){
  library(reticulate)
  #
  if(class(py_df_name) == "character"){
    if((class(try(py$i, T)) == "try-error") |
       (class(try(py$i, T)) != "try-error" & i_overwrite == TRUE)){
      try(py_run_string(paste("i = globals()['", py_df_name, "']",
                              sep = "")), T)
      if(class(try(class(py$i), T)) == "data.frame"){
        View(py$i, py_df_name)
        py_run_string("del i")
        #repl
        if(repl == T){repl_python()}
      }else{
        message(paste("   データフレーム'", py_df_name, "'が見つかりませんでした。",
                      sep = ""))
        try(py_run_string("del i"), T)
      }
    }else{
      message("   'i'が存在します。削除するか引数i_overwrite = TRUEを設定してください。")
    }
  }else{
    message("   引数py_df_nameを文字列で指定してください。")
  }
}

めちゃめちゃ苦戦してしまいました。まず引数(dataframe名)を「py$~」としてしまえば一瞬で終わるのですが、文字列で一致する名前のdataframeを持ってくるという形にしました。

動作としては、引数py_df_nameがcharacter(文字列)だった場合python内でiという変数にdataframeを移動して、それをView( )関数でみている形になります。

tryによって例外処理をいくつも作ってますね。解説めんど、、、

使ってみる

使い方は簡単。

> py_view("df")
>>> 

これだけで先ほどのViewerが表示され、pythonに戻ることができています。

 まとめ

以上!!!
思ったよりも無駄でした。

「library(reticulate)」をしておかないと「py$~」で躓くのとView( )内で毎回「py」を付けないといけないのをめんどくさいと思って作ってみましたが、結局この作業した方が速いんじゃないかって感じですね。

とりあえずRでの例外処理の練習になったのでよし。

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

【Python】アルゴリズムを意識したコード

アルゴリズムを意識したPythonコード

問題

ある学校の授業の時間割が与えられる。
1つの教室で同時に出来る授業は1つまで。
学校には最低いくつの教室が必要か求めるプログラムを実装せよ。
[(開始時間, 終了時間), ...]

なお、"解決①"の実装より処理速度の速い実装であること。

INPUT/OUTPUT

# 例題
classes1 = [(0, 50), (50, 100)]
answer1 = 1

classes2 = [(0, 50), (50, 100), (20, 70)]
answer2 = 2

classes3 = [(10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (60, 80)]
answer3 = 3

classes4 = [(0, 50), (50, 100), (20, 70), (50, 100)]
answer4 = 3

classes5 = [(0, 20), (10, 30), (20, 40), (30, 50)]
answer5 = 2

classes = [
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
    (10, 50), (20, 30), (30, 70), (60, 100), (70, 90), (10, 50), (20, 30), (30, 70), (60, 100), (70, 90),
]

解決①

def dec_speed(func):
    def wraps(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f'処理速度: {end - start}')
        return result
    return wraps


def check_time_overlaps(a: tuple, b: tuple) -> bool:
    return b[0] < a[1] < b[1]


@dec_speed
def solution(classes: list) -> int:
    num_classes = len(classes)
    max_classes = 1
    for i in range(num_classes):
        rooms = 1
        for j in range(num_classes):
            if i != j:
                check = check_time_overlaps(classes[i], classes[j])
                rooms += 1 if check else 0
        max_classes = max(max_classes, rooms)
    return max_classes

assert solution(classes1) == answer1
assert solution(classes2) == answer2
assert solution(classes3) == answer3
assert solution(classes4) == answer4
assert solution(classes5) == answer5
print('OK')


# 処理速度の確認
solution(classes)

回答

@dec_speed
def solution2(classes: list) -> int:
    timeline = []
    for start, end in classes:
        timeline.extend([(start, True), (end, False)])
    timeline = sorted(timeline)

    max_rooms = 0
    rooms = 0
    for _, is_start in timeline:
        rooms += 1 if is_start else -1
        max_rooms = max(max_rooms, rooms)
    return max_rooms

assert solution2(classes1) == answer1
assert solution2(classes2) == answer2
assert solution2(classes3) == answer3
assert solution2(classes4) == answer4
assert solution2(classes5) == answer5
print('OK')

# 処理速度の確認
solution2(classes)

参考Youtube

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

Cloud Vision API (GCP vision AI ) で "AttributeError: module 'google.cloud.vision' has no attribute 'types'"

概要

GCPの物体検出API1のチュートリアルを実行していたら,エラーが発生したので解決方法を執筆.
APIの認証方法は省略.
使用言語はPython.

エラーについて

チュートリアルの以下のコードを実行すると,AttributeErrorが発生する.
(下記コードはチュートリアルからコピペ)

def localize_objects(path):
    from google.cloud import vision
    client = vision.ImageAnnotatorClient()

    with open(path, 'rb') as image_file:
        content = image_file.read()
    image = vision.types.Image(content=content)

    objects = client.object_localization(
        image=image).localized_object_annotations

    print('Number of objects found: {}'.format(len(objects)))
    for object_ in objects:
        print('\n{} (confidence: {})'.format(object_.name, object_.score))
        print('Normalized bounding polygon vertices: ')
        for vertex in object_.bounding_poly.normalized_vertices:
            print(' - ({}, {})'.format(vertex.x, vertex.y))

エラー内容は以下の通り.
7行目で発生している.google.cloud.visionからtypesが削除されたらしいのでエラーが発生する.

AttributeError: module 'google.cloud.vision' has no attribute 'types'

解決策

visionから直接Imageを使えばOK

image = vision.types.Image(content=content) # エラー
image = vision.Image(content=content) # 解決

全体像は以下の通り,7行目しか変更していない

    """Localize objects in the local image.

    Args:
    path: The path to the local file.
    """
    from google.cloud import vision
    client = vision.ImageAnnotatorClient()

    with open(uri, 'rb') as image_file:
        content = image_file.read()
    image = vision.Image(content=content)

    objects = client.object_localization(
        image=image).localized_object_annotations

    print('Number of objects found: {}'.format(len(objects)))
    for object_ in objects:
        print('\n{} (confidence: {})'.format(object_.name, object_.score))
        print('Normalized bounding polygon vertices: ')
        for vertex in object_.bounding_poly.normalized_vertices:
            print(' - ({}, {})'.format(vertex.x, vertex.y))

余談

公式チュートリアルって意外とバグがあったりしますよね.
関係ないですが,GCPのImageはPillowのImageと被ってて若干使いづらいです.

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

Pythonでビン取りゲームを作ってみた

ビン取りゲームというゲームをPythonで作ってみた。
しかし何本とるかをランダムで決めているだけだからすごく弱い...

コード

BottlePickingGame.py
import random
import time
import sys

print("""
ビン取りゲームを始めます。ビン取りゲームとは、
ビンの本数を決めて交互にビンを取っていき、
最後のビンを取った人が負けです。
ビンの全体の最低本数は15本で、
1回に取れる本数は1本、または2本、または3本です。
""")

time.sleep(2)
Number=0
while 15>Number:
    try:
        Number=int(input("ビンは何本にしますか?\n"))

        if 15>Number: 
            print("ビンの最低本数は、15本です")
    except ValueError:
        print("半角数字で整数を入力してください")
remaining=0
remaining=int(remaining)
turn=0
take=0
take=int(take)
print("\n\nゲームを開始します")
time.sleep(1)
while Number>=remaining:
    print("\n残りの本数は、",Number-remaining,"本です",sep="")
    time.sleep(0.5)
    print("\nあなたの番です")
    turn=0
    take=0
    time.sleep(0.5)
    while take>3 or 0>=take:
        try:
            take=int(input("何本取りますか?\n"))
            if take>3 or 0>=take:
                print("取れる本数は、1〜3本です")
        except ValueError:
            print("半角数字で整数を入力してください")
    remaining=remaining+take
    print("\n残りの本数は、",Number-remaining,"本です",sep="")
    if remaining>=Number:
        break
    time.sleep(0.8)
    print("\nCPの番です")
    turn=1
    if Number>remaining+3:
        take=random.randint(1,3)
        take=int(take)
    elif Number>remaining+2:
        take=2
        take=int(take)
    elif Number>remaining+1:
        take=1
        take=int(take)
    else:
        take=1
        take=int(take)
    remaining=remaining+take
    time.sleep(0.5)
    print(take, "本取りました",sep="")
    if remaining>=Number:
        break
if turn==0:
    time.sleep(0.5)
    print("\n\nあなたの負けです...")
else:
    time.sleep(0.5)
    print("\n\nあなたの勝ちです!!")
time.sleep(1)
print("ゲームを終了します")
time.sleep(5)
sys.exit(0)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

poetryのuninstall

概要

備忘録。
poetryのpoetry self updateを行った後、poetryを実行したら、あらゆるpackageが足りないと無限にerrorを吐かれたので、
仕方なく、poetryをuninstallした後、installをし直した。

公式ドキュメントにて詳細は参照されており、本記事もそれに参考してます。
https://python-poetry.org/docs/#:~:text=If%20you%20see%20something%20like,variable%20before%20executing%20the%20installer.

前提

  • How to install poetry
$ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -

Uninstall方法

  • How to uninstall poetry
$ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python - --uninstall

以上、解散

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

tif形式の気象データをnumpy配列に変換する

やりたいこと

  • tif形式で格納されている気象データをnumpy.arrayに変換しpythonでかんたんに利用できるようにしたい!

そもそもtif形式の気象データ とは?

世界全体の気温や降水量などの数値が格納されているデータ.ラスタデータと呼ばれる.
世界地図をグリッド(1マスを1度,0.5度など様々な粒度がある)に区切り,その1マス毎に気象データを格納されている.

例:worldclim

WorldClimの[Historical climate data]
1970-2000年までの1−12月の月平均数値を記録したもの.最低/最高/平均気温,降水量などのデータが10m~30sまでの粒度で利用できる.

とりあえず触ってみる.

今回はとりあえずminimum temperature-5minutesをダウンロードしてみる.

ちなみに

5minutesとは,1辺が緯度経度で言う1/12度の大きさ(1hで1度)の正方形グリッドで世界地図を区切ったもの.
世界地図は緯度が90~-90,経度が180~-180の180*360の長方形で表すことができる.今回のデータは1/12度で1マスなので,縦2160*横4320マスである.
以下のQiitaの記事に詳しい記載があったので,詳細はそちらを.とてもわかり易い.
- GISで使う経緯度の基礎知識とかTIPSとか - Qiita

ダウンロードして展開すると,以下のような[wc2.1_5m_tmin_XX.tif]と名前のついた12のtifファイルが確認できる.それぞれが1−12月の30年分の月最低気温データになっている.
スクリーンショット 2020-10-23 14.25.50.png

なお今回の粒度だと1ファイルあたりのサイズは11MB程度と可愛いが,これが30s(現在の10倍の粒度)とかになるとファイルサイズも可愛くなくなってくるので注意してほしい.

QGISを使ってデータを取り出す

QGISとはオープンソースGISソフトで,地理情報システムの一種だ.今回のような気象データや地形データなど,様々な地理データを扱うことに向いている.今回はtifファイルをnumpyに変換することが目的なので詳しくは触れない意が,興味がある方は以下を参照していただきたい.
- QGISビギナーズマニュアル

かんたんな使い方

  • QGISをダウンロードし起動,CMD+Nまたはメニューボタンで新規プロジェクトを開く.
  • 左側にあるレイヤウィンドウに先程のtifファイルをドラッグアンドドロップする.
  • 世界地図が出た!色の濃淡がデータの数値の大小を表している.
    スクリーンショット 2020-10-23 14.34.46.png

  • 右クリックしtifファイルの情報を確認できる.ファイルの幅と高さを確認し,自分の求めているスケールと一致しているか確認する.

ここからは実際にラスタデータをnumpy配列に変換していく.

QGIS内のPythonコンソールを使っていく.

メニューバーにあるPythonアイコンをクリックするとIpythonコンソールが起動される.
これ以降の操作はラスターデータの読み込み · GIS実習オープン教材を参考にした.

numpyに変換する.

はじめに必要なライブラリーをインポートする.

from osgeo import gdal
import numpy as np

次に読み込みたいtifファイルのパスを与え,開く.

uri = 'YOUR_PASS/wc2.1_5m_tmin/wc2.1_5m_tmin_01.tif'
src = gdal.Open(uri, gdal.GA_ReadOnly)
res = src.GetRasterBand(1)
res_arr = res.ReadAsArray()

これでres_arrにnumpy配列が格納されたはずだ.
確認してみよう.

>>> res_arr
array([[-3.4000000e+38, -3.4000000e+38, -3.4000000e+38, ...,
        -3.4000000e+38, -3.4000000e+38, -3.4000000e+38],
       [-3.4000000e+38, -3.4000000e+38, -3.4000000e+38, ...,
        -3.4000000e+38, -3.4000000e+38, -3.4000000e+38],
       [-3.4000000e+38, -3.4000000e+38, -3.4000000e+38, ...,
        -3.4000000e+38, -3.4000000e+38, -3.4000000e+38],
       ...,
       [-1.9660000e+01, -2.1010000e+01, -2.1010000e+01, ...,
        -2.2410000e+01, -2.2410000e+01, -1.9708000e+01],
       [-2.2403999e+01, -2.3939999e+01, -2.3939999e+01, ...,
        -2.5539999e+01, -2.5539999e+01, -2.2451000e+01],
       [-1.6927999e+01, -1.8080000e+01, -1.8080000e+01, ...,
        -1.9279999e+01, -1.9279999e+01, -1.6963999e+01]], dtype=float32)

問題なさそうだ.ちなみに値が-3.4000000e+38となっている箇所は,陸地がなく,データが入っていないNAのマスだ.
念の為arrayのshapeも確認しておく.

>>> res_arr.shape
(2160, 4320)

先程プロパティで確認したサイズと一致している.
np.saveで保存すればnumpyarrayとして利用できる.

おまけ:12ヶ月の平均データを取る.

ここまでくればあとは簡単で,01~12までのtifを読み込んで平均をとってやれば
目的のデータは入手できるはずだ.

dict = {}
from osgeo import gdal
import numpy as np
for a in range(1,13):
    uri = '/YOUR_PASS/wc2.1_5m_tmin/wc2.1_5m_tmin_'+str(a).zfill(2)+'.tif'
    print(uri)
    src = gdal.Open(uri, gdal.GA_ReadOnly)
    res = src.GetRasterBand(1)
    res_arr = red.ReadAsArray()
    dict[a] = res_arr
res = np.stack(dict.values(),axis=1)
data_m = np.mean(res, axis=1)
np.save('/YOUR_PASS/wc2.1_5m_tmin/mean.npy', data_m)

これで完成.

以上です.はじめ検索した際にやり方がまとまったサイトを見つけられなかったのでまとめておきました.

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

[Python] Python文法のサンプルコード

はじめに

Pythonの勉強のために以下のようなプログラムを書きましたが、
もっと効率的な書き方があると考えているので、
有識者の方々の意見が聞きたく記事にしました。
もっと行数短く書けるよ!っていう有識者の方々、是非ともコメント頂きたいです。

和暦変換

和暦変換
# 年数(西暦)で入力
year=int(input())

Syear=[1868, 1912, 1926, 1989, 2019, 9999]
wareki=['明治', '大正', '昭和', '平成', '令和', '']

i=0

# 和暦変換
while Syear[i]<=year:
   Wyear=year-Syear[i]+1
   Wname=wareki[i]
   i+=1

# 出力
print(str(year)+'年は'+Wname+str(Wyear)+'年')

年齢判定

年齢判定
import datetime

# 生年月日(西暦)を入力
Tymd=input()

# 年月日に分割
param=Tymd.split('/')
Tyear=int(param[0])
Tmonth=int(param[1])
Tday=int(param[2])

# 現在の日付を取得
Today=datetime.datetime.now()
year=Today.year
month=Today.month
day=Today.day

# 年齢計算
age=int(year)-int(Tyear)
if int(month) < int(Tmonth):
    age-=1
else:
    if month==Tmonth:
        if day < Tday:
            age-=1

# 出力
print(Tymd+'の人は'+str(age)+'歳')

曜日判定

曜日判定
import math

# 入力値(例: 1997/9/11)をうけとる
x=input()

# 入力値を年月日に分割
y=x.split('/')
year = int(y[0])
month=int(y[1])
day=int(y[2])

# 出力
print(str(year)+'年'+str(month)+'月'+str(day)+'日は', end='')

if month < 3:
    month=month+12
    year=year-1

# 曜日を求める
weekday = (year + math.floor(year / 4) - math.floor(year / 100) + math.floor(year / 400) + math.floor((13*month+8) / 5) + day) % 7
week = ['日', '月', '火', '水', '木', '金','土']

# 出力
print(str(week[weekday])+'曜日です')

複数桁の桁分解

複数桁の桁分解
# 入力値をうけとる
x=int(input())

num=[]

# 1桁ごとに分解
while x>0:
    num.append(x%10)
    x//=10
num.reverse()

# 出力
for i in range(len(num)):
 print(num[i], end=' ')

閏年判定

閏年判定
# 年数(西暦)を入力
year=int(input())

# 閏年判定
if year%4==0:
    if year%100==0:
        if year%400==0:
            print(str(year)+'年は閏年')
    else:
        print(str(year)+'年は閏年')
else:
    print(str(year)+'年は閏年ではない')

干支判定

干支判定
# 年数(西暦)を入力
year=int(input())

# 全干支
Alleto =["申(さる)", "酉(とり)", "戌(いぬ)", "亥(いのしし)", "子(ねずみ)"
            , "牛(うし)", "寅(とら)", "卯(うさぎ)", "辰(たつ)", "巳(へび)", "午(うま)", "未(ひつじ)"]

# 干支判定&出力
print(str(year)+'年の干支は'+Alleto[year%12])

素数判定

素数判定
# 2以上の数値を入力
num=int(input())

# 0: 素数
# 1: 素数ではない
sosu=0

# 素数判定
for i in range(2, num):
    if num%i==0:
        sosu=1
        break

# 出力
if sosu==0:        
    print(str(num)+'は素数である')
elif sosu==1:
    print(str(num)+'は素数ではない')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

matplotlibで複数系列をまとめた棒グラフを描画する方法

ちょうどよい記事がなかったので備忘録として。

# version
matplotlib == 3.2.2
import numpy as np
import matplotlib.pyplot as plt

# 縦棒
def barplot(ax, labels, datas):
    x = np.arange(len(labels))  # the label locations
    width = 0.35  # the width of the bars

    rects1 = ax.bar(x - width/2, datas[0]["val"], width, label=datas[0]["label"])
    rects2 = ax.bar(x + width/2, datas[1]["val"], width, label=datas[1]["label"])

    # Add some text for labels, title and custom x-axis tick labels, etc.
    ax.set_xticks(x)
    ax.set_xticklabels(labels)

# 横棒
def barhplot(ax, labels, datas):
    x = np.arange(len(labels))  # the label locations
    width = 0.35  # the width of the bars

    rects1 = ax.barh(x - width/2, datas[0]["val"], width, label=datas[0]["label"])
    rects2 = ax.barh(x + width/2, datas[1]["val"], width, label=datas[1]["label"])

    # Add some text for labels, title and custom x-axis tick labels, etc.
    ax.set_yticks(x)
    ax.set_yticklabels(labels)
    plt.gca().invert_yaxis()

使い方は以下の通り。

labels = ['G1', 'G2', 'G3', 'G4', 'G5']
datas = [
    {"label": "men1", "val": [20, 34, 30, 35, 27]},
    {"label": "women2", "val": [25, 32, 34, 20, 25]}]
fig, ax = plt.subplots()
# barhplot(ax, labels, datas)
barplot(ax, labels, datas)
plt.legend()
plt.show()
  • barplot
    tate.png

  • barhplot
    yoko.png

3以上の場合には、widthを調整して並べる必要がある。

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

Codeforces Round #673 (Div. 2) バチャ復習(10/22)

今回の成績

スクリーンショット 2020-10-22 18.05.52.png

今回の感想

D問題が解けそうで解けなかったのでムカついて記事を書いています。

[追記]
よく見たら自明な条件を見逃していました。解法に固執し過ぎました…。頭が硬すぎる…。

A問題

$i,j$を選び$a_j$に$a_i$を足すので最小の$a_i$を選んで操作を行うのが最大の操作回数です。したがって、$i \neq j$のもとで任意の$j$について操作を行うときの最大の操作回数を考えます。$k$を超えないので、それぞれの$j$で$[\frac{k-a_j}{a_i}]$が際だの操作回数になります。

A.py
for _ in range(int(input())):
    n,k=map(int,input().split())
    a=list(map(int,input().split()))
    m=min(a)
    l=a.index(m)
    ans=0
    for i in range(n):
        if i!=l:
            ans+=(k-a[i])//m
    print(ans)

B問題

$f(b)=(b_i+b_j$=$T$となる$i<j$の組の総数)であり、$a$を分割した$c,d$の$f(c)+f(d)$を最小にすることを考えます。ここで、$x$を$T-x$とは異なる方の配列に入れればいずれも0にすることができるのではと考えました。

$x \neq T-x$であれば必ず成り立たせられますが、$x=T-x \leftrightarrow x=\frac{T}{2}$となる$x$が三つ以上存在する場合は0にすることができず、$x=\frac{T}{2}$となる要素を$k$個として$c,d$にそれぞれ$\frac{k}{2},k-\frac{k}{2}$個ずつ振り分ける時に$f(c)+f(d)$は最小になります。

よって、以下のような場合分けをします。

(1)$T$が奇数のとき
$[\frac{T}{2}]$以下の要素を$c$に,$[\frac{T}{2}]$より大きい要素を$d$に入れれば良いです。

(2)$T$が偶数のとき
$[\frac{T}{2}]$未満の要素を$c$に,$[\frac{T}{2}]$より大きい要素を$d$に入れた元で$[\frac{T}{2}]$の要素のインデックスを$cand$に一旦入れます
$cand$のサイズが先ほどの$k$なので、$c,d$にそれぞれ$\frac{k}{2},k-\frac{k}{2}$個ずつ振り分けます。

B.py
for _ in range(int(input())):
    n,t=map(int,input().split())
    a=list(map(int,input().split()))
    ans=[-1]*n
    cand=[]
    for i in range(n):
        if t%2==0:
            if a[i]<t//2:
                ans[i]=0
            elif a[i]>t//2:
                ans[i]=1
            else:
                cand.append(i)
        else:
            if a[i]<=t//2:
                ans[i]=0
            else:
                ans[i]=1
    l=len(cand)
    for i in range(l):
        if i<l//2:
            ans[cand[i]]=0
        else:
            ans[cand[i]]=1
    print(" ".join(map(str,ans)))

C問題

少し実装が面倒でしたが、実装するだけです。Bより方針は立ちやすいと思います。

ある長さ$k$の任意の連続部分列に含まれる要素で最小の要素を任意の$k$で求めます。まず、$k$が大きくなるにつれて単調減少するのは自明です。この時、それぞれの値がどのタイミングで出てくるか(主客転倒!)に注目すると、ある値の条件を満たす長さ$k$の連続部分列のうち最小の長さ$k$を求めれば良いのではと考えました。

したがって、ある値$x$となるインデックスの集合$x_1,x_1,…,x_l$(昇順で0-indexed)があり、さらに$x_0=-1,x_{l+1}=n$とした時、任意の$0 \leqq i \leqq {l}$で$x_{i+1}-x_i \leqq mi$が成り立てば良いです。よって、$mi=max(x_{i+1}-x_i)$が解です。(これは、図を書くと成立条件が見えやすいと思います。)

よって、それぞれの値で題意の最小の長さを求めることができるので($O(n)$)、あとは答えとなる最小の要素を求めることを考えます。つまり、実装は以下のようになります。

$values$=(<値,題意の連続部分列の長さの最小値>のペアを値の昇順で保存した配列)
$now$=(最小の要素が決まってない中で最長の長さ)
$ans[i]$=(長さ$i+1$の任意の連続部分列に含まれる要素で最小の要素)

最小の要素から順に決めていけば良いので、$i$の昇順で$values$の$i$番目の要素を見ていくことを考えます。この時、$values[i].S$が$now$以下の時、$now$から$values[i].S$は$values[i].F$とすれば良いです。そして、$now$は$values[i].S-1$に更新します。また、$values[i].S$が$now$より大きいときは更新の処理を行わずに次の$values$の要素を見れば良いです。また、最小の要素が決まらない長さ$k$の部分列が存在する場合は$ans[k-1]$を-1とする必要があるので、初めに$ans$は-1で初期化しておきます。

C.cc
//デバッグ用オプション:-fsanitize=undefined,address

//コンパイラ最適化
#pragma GCC optimize("Ofast")

//インクルードなど
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

//マクロ
//forループ
//引数は、(ループ内変数,動く範囲)か(ループ内変数,始めの数,終わりの数)、のどちらか
//Dがついてないものはループ変数は1ずつインクリメントされ、Dがついてるものはループ変数は1ずつデクリメントされる
//FORAは範囲for文(使いにくかったら消す)
#define REP(i,n) for(ll i=0;i<ll(n);i++)
#define REPD(i,n) for(ll i=n-1;i>=0;i--)
#define FOR(i,a,b) for(ll i=a;i<=ll(b);i++)
#define FORD(i,a,b) for(ll i=a;i>=ll(b);i--)
#define FORA(i,I) for(const auto& i:I)
//xにはvectorなどのコンテナ
#define ALL(x) x.begin(),x.end() 
#define SIZE(x) ll(x.size()) 
//定数
#define INF 1000000000000 //10^12:∞
#define MOD 1000000007 //10^9+7:合同式の法
#define MAXR 100000 //10^5:配列の最大のrange
//略記
#define PB push_back //挿入
#define MP make_pair //pairのコンストラクタ
#define F first //pairの一つ目の要素
#define S second //pairの二つ目の要素

signed main(){
    //小数の桁数の出力指定
    //cout<<fixed<<setprecision(10);
    //入力の高速化用のコード
    //ios::sync_with_stdio(false);
    //cin.tie(nullptr);
    ll t;cin>>t;
    REP(_,t){
        map<ll,vector<ll>> m;
        ll n;cin>>n;
        REP(i,n){
            ll a;cin>>a;
            m[a].PB(i);
        }
        vector<pair<ll,ll>> values;
        FORA(i,m){
            ll s=SIZE(i.S);
            ll mi=-1;
            REP(j,s){
                if(j==0){
                    mi=max(mi,i.S[j]+1);
                }
                if(j==s-1){
                    mi=max(mi,n-i.S[j]);
                }
                if(j!=s-1){
                    mi=max(mi,i.S[j+1]-i.S[j]);
                }
            }
            values.PB(MP(i.F,mi));
        }

        vector<ll> ans(n,-1);
        ll sv=SIZE(values);
        #if 0
        REP(i,sv){
            if(i==sv-1)cout<<values[i].F<<" "<<values[i].S<<"\n";
            else cout<<values[i].F<<" "<<values[i].S<<endl;
        }
        cout<<endl;
        #endif
        ll now=n;
        REP(i,sv){
            while(values[i].S<=now){
                ans[now-1]=values[i].F;
                now--;
            }
        }
        REP(i,n){
            if(i==n-1)cout<<ans[i]<<"\n";
            else cout<<ans[i]<<" ";
        }
    }
}

D問題

$3n$回以下で自由に操作を行うことができるので、都合の良い場合を考えます。まず気づくのが、$i=1$とすれば値を調整することができるということです。また、$\sum_{i=1}^{n}{a_i}$が$n$の倍数であるときは達成できず、他の場合は達成できるものとして考えました。

ここで、$i=1$を選択した時に$a_1$は減る方向の変化をするので、$i=1$にできるだけ要素を集めてから振り分ける方法が良いのではと考えました(ここからの考察を詰め切ることができませんでした)。

また、$a_i$から$a_1$へと要素を移動させる時に$a_i$は$a_i \ mod \ i$だけ余ってしまいます。ここで、この余りの分も$a_1$に移すには一旦$a_1$から$a_i$に$i- a_i \ mod \ i$だけ移しておけば、再度$a_i$から$a_1$に移すことで$a_i=0$を達成することができます。この時、$a_1\geqq i- 1\geqq i- a_i \ mod \ i$を満たす必要がありますが、$i$の昇順で$a_i$から$a_1$へ要素を移動させたものとすると、$a_1=\sum_{j=1}^{i-1}a_i \geqq i-1$よりこの条件を満たします。

よって、$i$の昇順で上記を行って$a_1 =\sum_{i=1}^{n}{a_i}$とした(最大$2(n-1)$回)後に任意の$i$について$a_1$から$a_i$へと$b(=\frac{\sum_{i=1}^{n}{a_i}}{n})$だけ要素を移動させる(最大$n-1$回)ことで、全体で最大$3(n-1)$回で全要素を$b$に等しくすることができます。

D.py
for _ in range(int(input())):
    n=int(input())
    a=list(map(int,input().split()))
    ans=[]
    if sum(a)%n!=0:
        print(-1)
        continue
    b=sum(a)//n
    for i in range(1,n):
        if a[i]%(i+1)==0:
            ans.append([i+1,1,a[i]//(i+1)])
            a[0]+=a[i]
            a[i]=0
        else:
            x=i+1-a[i]%(i+1)
            ans.append([1,i+1,x])
            a[0]-=x
            a[i]+=x
            ans.append([i+1,1,a[i]//(i+1)])
            a[0]+=a[i]
            a[i]=0
    for i in range(1,n):
        ans.append([1,i+1,b])
    l=len(ans)
    print(l)
    for i in range(l):
        print(" ".join(map(str,ans[i])))

E問題以降

今回は飛ばします。

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

はやぶさ2の軌道を可視化する

はやぶさ2の軌道データ

先日(2020/10/21),「はやぶさ2」の軌道情報が公開されました.
打ち上げから地球帰還直前(TCM-3)までの「はやぶさ2」の軌道

上の記事に"hayabusa2_orbit_20201021.txt"というファイルが添付されています.
中身には,2014年の打上げ以降の,時刻・打上げからの起算日・はやぶさ2・地球・りゅうぐうの座標(太陽中心)などが記載されています.軌道精度は1000kmのため研究等には使えないと思います.

本記事では公開された軌道データを用いて,はやぶさ2やりゅうぐうの軌道をアニメーションで可視化したいと思います.
(やっていることは,ファイルを読み込んで数値データをグラフ化しているだけです)

ソースコードはgithubで公開しています(Pythonで実装しています).
https://github.com/motthi/hayabusa2_orbit

太陽中心座標での軌道

往路

absoluteOrbit.gif

復路

absoluteOrbit_back.gif

地球中心座標での軌道

はやぶさ2と地球,りゅぐうと地球のデータの差を取れば地球から見た軌道も分かります.

往路

relative_e.gif

復路

relative_e_b.gif

はやぶさ2の今後

カプセルの地球帰還日は2020/12/6です.探査機本体はその後も運用を続け,2031年に1998 KY26という小惑星に向かう探査ミッション(EAEEAシナリオ)が計画されています.

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

geopandasのインポートでOSError: could not find or load spatialindex_c-64.dll

geopandas のインポートでコケた

OSError: could not find or load spatialindex_c-64.dll

https://github.com/conda-forge/geopandas-feedstock/issues/78
ここによると、チャンネルは混ぜるな危険?ということらしい・・・

私の環境では、Pythonはデフォルトチャンネル、
geopandasをインストールしたのはconda-forgeだったのが原因らしい。

conda install python -c conda-forge

これで解決!
ということは逆のパターンもあるのかな・・・

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

複数のpythonのバージョンを1つの jupyter で扱う

Overview

  • python のバージョンを複数使いたい(venv含む)
  • けど、それぞれのバージョンに jupyter 入れるのは効率が悪い
  • ので、どれか1つのバージョンに jupyter を入れて kernel だけ選べるようにしたい

ときってあるじゃないですか。

具体的に

こういう状況で、

bash
$ pyenv versions
  system
  3.6.12
* 3.8.6 (set by /Users/kuryu/.pyenv/version)

3.8.6 で jupyter を起動して、 3.6.12 の kernel を動かすのを目指す。

まず jupyter を入れる

bash
$ python -V
Python 3.8.6

$ pip install jupyter

python のバージョンを変更する

bash
$ pyenv global 3.6.12

$ python -V
Python 3.6.12

必要なら venv を作成

pyenv 環境にそのまま構築することも可能だけど、今回は venv 作ります。

bash
$ python -m venv .venv

$ . .venv/bin/activate

(.venv) $ python -V
Python 3.6.12

(.venv) $ pip list
Package    Version
---------- -------
pip        18.1
setuptools 40.6.2
You are using pip version 18.1, however version 20.2.4 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

ipykernel を入れる

bash
(.venv) $ pip install ipykernel

(.venv) $ ipython kernel install --user --name=hoge

仮想環境を出て

bash
(.venv) $ deactivate

python のバージョンも戻す

bash
$ pyenv global 3.8.6

$ python -V
Python 3.8.6

おもむろに jupyter を起動

bash
$ jupyter notebook

kernel が追加されているので

スクリーンショット 2020-10-22 19.06.29.png

実行してバージョンを確認

3.8.6 で起動した jupyter の中で 3.6.12 の kernel を起動できました。

ちなみに、スクショ撮り忘れちゃったんですけど、

jupyter
sys.executable

とかやると、ちゃんと venv 環境の python のパスが表示されます。

スクリーンショット 2020-10-22 19.01.03.png

kernel が不要になったら

bash
$ jupyter kernelspec uninstall hoge

っていう話なんですけど、まぁまぁややこしいので混乱しないように注意。

cf.

https://qiita.com/Gattaca/items/80a5d36673ba2b6ef7f0

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

tkinterで超簡単にGUIアプリを作る

前置き

Pythonの人気が上昇してきたためか、Pythonの需要が最近ではとても高まっています。Pythonでは機械学習などの人工知能に関する技術が強いイメージですが、GUIのアプリを作ることも出来ます。
Pythonを始めたばかりでライブラリ?モジュール?という方向けの記事なので、中級者の方には物足りないかもしれませんが、読んでいただけると嬉しいです。

ファイルの準備

tkinter-gui/
├ app.py
├ face.png

上のようにapp.pyを作成して下さい。フォルダ名を自分はtkinter-guiとしましたが、何でも良いです。
face.pngは以下の画像を保存してapp.pyと同じ場所において下さい。

image.png
face.png

コードを書く

app.py
# Tkinterモジュールの読み込み
import tkinter

# ウィンドウの生成
root = tkinter.Tk()
root.attributes("-topmost", True)
root.minsize(width=200, height=200)

# Frameウィジェットの生成
frame = tkinter.Frame(root, width=300, height=300, bg="black")
frame.propagate(False)
frame.pack()

# テキストのLabelウィジェットの生成
label= tkinter.Label(frame, text="Hello! How are you?", fg="white", bg="black", font=("", 16))
label.pack()

# 画像のLabelウィジェットの生成
import os
png = tkinter.PhotoImage(file=os.path.dirname(__file__)+"/face.png")
image = tkinter.Label(frame, image=png, bg="black")
image.pack()

# Entryウィジェットの生成
entry = tkinter.Entry(frame, width=20, bg="gray", fg="white")
entry.insert(0, "happy")
entry.pack()

# クリックイベント関数
def show_text():
    new_label = tkinter.Label(frame, text=entry.get(), fg="white", bg="black", font=("", 32))
    new_label.pack()
    entry.destroy()
    button.destroy()

# Buttonウィジェットの生成
button = tkinter.Button(frame, text="Say", bg="gray", fg="yellow", command=show_text)
button.pack()

# ウィジェットが配置されたウィンドウを表示
root.mainloop()

上のコードをapp.pyに書いて下さい。
その後、 app.pyを実行すると以下のようなウィンドウが立ち上がります。

image.png

トカゲがあなたの気分を聞いていますね。テキストボックスに入力した後にSayボタンを押して答えてあげましょう。

image.png

happyと答えることが出来ました。

最後に

アプリケーションに必要なのは、

  1. ユーザーからの入力をプログラムが受け取る
  2. 入力に対してプログラムが何らかの処理をする
  3. プログラムはユーザーに出力を返す

この流れです。

今回は、本当に必要最低限なアプリケーションとしての機能のみを実装しました。この上のコードを土台に色々と改変を施せば、電卓アプリなどを作ることも可能です。コードの1行1行が画面上に何を出力しているのかを理解し、自分だけのアプリケーションを作ってみましょう。

今回の記事の内容は、下のサイトでも詳しく説明しています。良かったら見て下さい。

【Tkinter入門】PythonでGUIアプリを作ってみよう!

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

デバッグ実行時にsudoを付ける方法

事象

Visual Studio CodeでPythonをデバッグ実行したときに、[Errno 13] Permission deniedが発生した。

image.png

原因

対象Pythonコードに管理者権限が必要だったが、Visual Studio Codeのデバッグ実行時にsudoが付いていないため。(あたりまえ)

対処方法

launch.jsonに"sudo": trueを追記すればOK。

image.png

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

Python StatsD クライアントの構成

絶えず変化するソフトウェア開発環境で、拡張性の高い分散アプリケーションを構築して展開することは、道のりの半分に過ぎません。 残りの半分は、正確なメトリックを記録しながら、アプリケーションの状態とインスタンスを監視することです。

消費されているリソースの数や、特殊なプロセスによってアクセスされているファイルの数などを確認したい場合があります。これらのメトリックは、技術スタックの実行と管理に関する貴重な洞察を提供します。 これにより、設計したものの最終的なパフォーマンスを理解するためのレバレッジが得られ、最終的には最適化に役立ちます。

仕事を成し遂げるためのツールの大部分はすでにそこにありますが、今日はStatsDの詳細について話します。 独自のPythonStatsDクライアントをデプロイする方法、Pythonアプリケーションを監視するためにそれを使用する方法、そして最終的に記録されたメトリックをデータベースに保存する方法を説明させて頂きます。 その後、PrometheusまたはGraphiteを搭載したGrafanaダッシュボードにStatsDメトリックを表示したい場合は、 MetricFireデモを予約し無料トライアルをゲットしましょう。 しかし、その前に、まずはStatsDから始めましょう!

StatsDとは何か?

StatsDは、統計とシステムパフォーマンスメトリックを収集してリッスンするnode.jsプロジェクトです。 これらの統計はネットワークを介して送信され、さまざまな種類のメトリックデータを収集できます。 StatsDを使用する主な利点は、Grafana、Graphite、InfluxDBなどの他のツールと簡単に統合できることです。

StatsDの長所

  1. 優れた起動時間
  2. パーセンタイルの処理はサーバーによって行われ、一度に複数のインスタンスの集約ビューを提供します(同じサービスに対して)。
  3. クライアント側のオーバーヘッドが比較的低くなります。
  4. 接続の問題を防ぐためにすべてのデータを送信するためにUDPを採用しています。
  5. 短命のアプリを構築するときに、シンプルで効果的かつ簡単に実装できます。
  6. メトリックは到着時にのみサーバーにプッシュされるため、メモリ使用率が低くなります。

しかし…メトリックが来ると「プッシュ」されるとはどういう意味でしょうか?

主に、メトリックレポートには2つの実行モデルがあります。 プルモデルでは、監視システムは特定のHTTPエンドポイントでアプリを「スクレイプ」します。 StatsDで使用されるプッシュモデルでは、アプリケーションはメトリックを監視システムに送信します。

前提条件とインストール

1.まず、Python3.6以降とpipをシステムにインストールする必要があります。

Linux、Windows、またはmacOSシステムで次のコマンドを実行することにより、Pythonのインストールを確認できます。

$ python --version

インストールされていない場合は、システムのこれらのインストール手順を確認してください。

  1. StatsD、Flask、Flask-StatsDが必要になり、フラスコ関連のメトリックが自動的に収集されます。 それに加えて、virtualenvが必要になります。これは分離されたPython環境を作成するためのツールであり、SQLALCHEMYはサンプルデータベースです。
pip install StatsD, flask, Flask-StatsD, virtualenv, Flask-SQLAlchemy

Pipは、これらのパッケージの最新バージョンを自動的にインストールします。

StatsDパッケージを使って遊んでみましょう

基本タイマーを実装することから始めます。

import statsd
timer = statsd.Timer('Statsd_Tutorial_Application')
timer.start()
# we can do just about anything over here
timer.stop('TutorialTimer')

同様に、基本カウンターの場合:

import statsd
counter = statsd.Counter('Statsd_Tutorial_Application')
# again do anything random over here
counter += 1

Flaskによるモニタリング

このチュートリアルでは、Flaskで基本的なTo Doリストアプリケーションを設計し、操作メトリックを記録します。

完全なチュートリアルリポジトリは、Githubからフォークできます。

ステップ1:依存関係をインポートする-5〜12行目:

from flask import Flask, request
from flask import render_template
from flask import redirect
from flask_sqlalchemy import SQLAlchemy
import time
import statsd

ステップ2:Flaskアプリ、StatsdクライアントおよびDBを起動します-14〜23行目:

c = statsd.StatsClient('localhost',8125)
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)

タスククラスを作成し、DBモデルで定義します-行26〜35:

class Task(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.Text)
    done = db.Column(db.Boolean, default=False)

    def __init__(self, content):
        self.content = content
        self.done = False

db.create_all()
  1. 主キーとして整数を保持するDB列の変数IDを作成します。
  2. テキストのコンテンツ列を作成します。
  3. タスクが完了/解決されたかどうかを示すために、デフォルトがfalseのブール値のdone列を作成します。
  4. コンテンツと完了列を開始します。
  5. データベースを印刷可能な形式で返送してください。
  6. 新しいDBを作成して開始します。

ここで、タスクを追加します-行42-57:

@app.route('/task', methods=['POST'])
def add_task():
    start=time.time()
    content = request.form['content']
    if not content:
     dur = (time.time() - start) *1000
     c.timing("errortime",dur)
     c.incr("errorcount")
        return 'Error'

    task = Task(content)
    db.session.add(task)
    db.session.commit()
    dur = (time.time() - start) *1000
    c.timing("tasktime",dur)
    c.incr("taskcount")

このコードは、フォームから受け取ったタスクのコンテンツをPOSTリクエストに追加します。 ただし、ここで説明することがより重要なのは、追加されるメトリックレポートです。

  1. 機能が開始するとすぐに、基本タイマーが開始されます。
  2. コンテンツにエラーがある場合、エラーは基本カウンターでインクリメントされます。 同様に、エラーの時間が記録されます。 最終的に、エラーが返されます。
  3. DBがタスクを追加すると、関数が実行された完全な期間が計算され、インクリメンターが更新されます。 合計期間も更新されます。

タスクの削除-60行目から65行目:

@app.route('/delete/&lt;int:task_id&gt;')
def delete_task(task_id):
    task = Task.query.get(task_id)
    db.session.delete(task)
    db.session.commit()
    c.incr("deletecount")

上記のコードは、インクリメントのために削除カウントを基本カウンターに追加することにより、DBからのタスクの削除を実行します。

pyStatsDからMetricFireへのメトリックの送信

StatsDを使用してこれらのメトリックを記録するなどは、初心者には助かります。 ただし、より業界グレードの本番環境に対応する環境では、これらのメトリックは、グラフの保存と処理を容易にするサービスによって処理される必要があります。 これがGraphiteの出番です。

Graphiteの紹介:

Graphiteは、Webサイト、アプリケーション/その他のサービス、およびネットワークサーバーのパフォーマンスを追跡するために使用される監視ツールとして設計されています。 Graphiteは、テクノロジーの世界でセンセーションを巻き起こし、本質的に新世代の監視ツールに火をつけ、時系列データの保存と取得だけでなく、共有と視覚化もはるかに簡単にしました。

Graphiteは基本的に2つの操作を実行します。

  • 数値の時系列データを保存する
  • このデータのグラフをオンデマンドでレンダリングする

Graphiteは収集エージェントではないため、収集エージェントのように扱うべきではありません。むしろ、測定値を時系列DBに取り込むためのより簡単なパスを提供します。 サーバーまたはローカルマシンからすでに実行されているgraphiteインスタンスへのメトリックの送信をテストするには、次の1行のコマンドを実行します。

`$ echo "foo.bar 1 `date +%s`" | nc localhost 2003`

インストールしたら、StatsDでメトリックをログに記録するだけで、ログに記録されたすべてのデータをGraphiteが取得します。 さて、Graphiteは大したことのようですが、開発者が解決したいと思っているGraphiteの特定のフォールバックがまだあります。 これがMetricFireの出番です。

なぜMetricFire:

  1. Graphite-as-a-Serviceを提供します
  2. 現在のGraphite版のギャップを埋めるために組み込みのエージェントを追加しました
  3. チームアカウントがコラボレーションに関する以前の問題を解決できるようにします
  4. カスタムの詳細なダッシュボード権限
  5. AWS、Herokuなどの他のサービスとの素晴らしい統合
  6. APIを介した操作は、開発を強化します
  7. ダッシュボードとユーザーデータの1時間ごとのバックアップ
  8. 必要に応じて簡単かつ迅速にスケーリング
  9. Graphiteモニタリングにおける確かな実績
  10. 経験豊富なエンジニアによる24時間年中無休のサポート
  11. プラグアンドプレイモデルで簡単に利用可能

しかし、それでも自己ホスト型および自己管理型のサービスを希望し、すべてを完全に制御したい場合は、StatsDとdockerを使用してgraphiteを起動するのが簡単な方法です。

デプロイメント

StatsDは、お好みのアーキテクチャやその他のサービス/マイクロサービスを使用して、お好みの環境にデプロイできます。 StatsDサーバーにメトリックを送信するすべてのクライアント側アプリがStatsDサーバーに到達可能であることを確認してください。そうすれば、StatsDはそれについて文句を言いません。

ジャストイン:AWS Cloudwatchは、インフラストラクチャのホスティングにAWSクラウドを採用している場合に備えて、StatsDメトリクスもサポートするようになりました。

私たちが蓄積したメトリックの視覚化に関する限り、Grafanaはその事実上の可視化ツールです。

自分で試してみたい場合は、デモにサインアップして、私たちはあなたに最適なgraphiteやPrometheusのモニタリングソリューションについて話すことができます。お気軽に、予約してください。

StatsD API

Python StatsDクライアントにもAPIが付属しており、それを統合したい場合はHTTPリクエストを介してトリガーできます。

参照

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