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

Python,Numpyで非線形座標変換をする時のBilinear補間関数

目的

画像処理における座標変換では,変換後の画像の(i,j)のindexに該当する元画像の座標を逆変換を用いて求めて,Bilinear等の手法で補間するのが一般的です。

CやC++等では仕方なしに2重ループを回していたのですがNumpyとPythonではforループは極力使いたくないと思っていた所,効率の良さそうなコードをGithubのとあるページで発見したので自分なりに変えてみました。

言うまでも無いことですが,Affineだったり射影変換の場合はそれ専用のOpenCVのライブラリを使ったほうが早いです。

コードの流れ

1. 変換により参照される元画像の座標も求める

変換前の(i,j)の座標indexを記録した配列を作ります。
arangeを用いて配列を作成し,tileやrepeatを用いて重ね合わせればあっという間にできますね。

以下の例では画像中心cx,cyから差し引いて,正規化した後に球面極座標に変換するために円周率をかけてたりしますが同じことです。

xline = (np.arange(wid)-cx)*2*pi/wid
yline = -(np.arange(hei)-cy)*pi/hei
Xsline = np.tile(xline,hei)
Ysline = yline.repeat(wid,axis=0)

あとは,これらの配列に対して様々な非線形変換を当てはめればOKです。
正弦余弦や対数など簡単な関数ならnumpyに用意があるので配列にそのまま渡せば全て一様に処理してくれてfor文を書くよりも断然早いです。

2. 求めた座標を基に変換後の画像をBilinear補間で求める

先程求めた座標に対応するピクセル値を補間でもってきて最後に画像の形にReshapeしておしまいです。

以下のコードでは,ピクセルindexの最大値,最小値を超えた場合はループさせていますが,一般的には0などの値でうめる(Padding)するのが良いでしょう。

def _bilinear_interpolation_loop(frame, screen_coord):
    ''' Calculate 
    input: frame and its subpixel coordinate [x,y]
    '''
    frame_height,frame_width,frame_channel =frame.shape
    #uf = np.mod(screen_coord[0],1) * frame_width  # long - width
    #vf = np.mod(screen_coord[1],1) * frame_height  # lat - height

    x0 = np.floor(screen_coord[0]).astype(int)  # coord of pixel to bottom left
    y0 = np.floor(screen_coord[1]).astype(int)
    uf = screen_coord[0]  # long - width
    vf = screen_coord[1]  # lat - height
    x2 = np.add(x0, np.ones(uf.shape).astype(int))  # coords of pixel to top right
    y2 = np.add(y0, np.ones(vf.shape).astype(int))

    # Assume Loop 
    x2 = np.where(x2 > frame_width-1, 0, x2)
    y2 = np.where(y2 > frame_height-1, 0, y2)

    base_y0 = np.multiply(y0, frame_width)
    base_y2 = np.multiply(y2, frame_width)

    A_idx = np.add(base_y0, x0)
    B_idx = np.add(base_y2, x0)
    C_idx = np.add(base_y0, x2)
    D_idx = np.add(base_y2, x2)

    flat_img = np.reshape(frame, [-1, frame_channel])
    #print(flat_img.shape)
    A = np.take(flat_img, A_idx, axis=0)
    B = np.take(flat_img, B_idx, axis=0)
    C = np.take(flat_img, C_idx, axis=0)
    D = np.take(flat_img, D_idx, axis=0)

    wa = np.multiply(x2 - uf, y2 - vf)
    wb = np.multiply(x2 - uf, vf - y0)
    wc = np.multiply(uf - x0, y2 - vf)
    wd = np.multiply(uf - x0, vf - y0)
    #print(wa,wb,wc,wd)
    # interpolate
    AA = np.multiply(A.astype(np.float32), np.array([wa, wa, wa]).T)
    BB = np.multiply(B.astype(np.float32), np.array([wb, wb, wb]).T)
    CC = np.multiply(C.astype(np.float32), np.array([wc, wc, wc]).T)
    DD = np.multiply(D.astype(np.float32), np.array([wd, wd, wd]).T)
    out = np.reshape(np.round(AA + BB + CC + DD).astype(np.uint8), [frame_height, frame_width, 3])
    return out

実行例

以下のブログにて正距円筒図法の画像を元に全天球カメラを仮想的に回転させた際の画像を作成していました。

https://ossyaritoori.hatenablog.com/entry/2019/12/10/RICOH_THETA_SC%E3%81%A7%E5%A4%9A%E9%87%8D%E9%9C%B2%E5%85%89%E3%83%BB%E5%90%88%E6%88%90%E3%82%92%E3%81%97%E3%81%A6Pixel4%E3%81%BF%E3%81%9F%E3%81%84%E3%81%AB%E3%82%AF%E3%83%AA%E3%82%A2%E3%81%AA

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

PyTorch入門(1)自動微分とか

Pytorch入門

ChainerがPyTorchへの移行を発表したということで、国内でもTensorFlowからPyTorchへの流れが強まるのではと思い、チュートリアルをこなしてみる。

インストール

公式 を参照。

チュートリアル

公式の通りに試してみる。

1.PyTorchとは

書き方はかなりNumpy-like。

$ x = torch.empty(5, 3)
$ print(x)
tensor([[2.4835e+27, 2.5428e+30, 1.0877e-19],
        [1.5163e+23, 2.2012e+12, 3.7899e+22],
        [5.2480e+05, 1.0175e+31, 9.7056e+24],
        [1.6283e+32, 3.7913e+22, 3.9653e+28],
        [1.0876e-19, 6.2027e+26, 2.3685e+21]])

メソッドに_(アンダーバー)を接尾することで破壊的な処理となる。

$ x = torch.tensor([5.5, 3])
$ y = torch.tensor([2.5, 5])
$ y.add_(x)
$ print(y)
tensor([8., 8.])

CPU-GPU間のテンソルのやり取りが非常に簡単。

if torch.cuda.is_available():
    device = torch.device("cuda")          
    y = torch.ones_like(x, device=device)  
    x = x.to(device)                       
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))       # ``.to`` can also change dtype together!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

公式ドキュメントをちゃんと読まない私がDjangoの静的ファイルについて理解するまで

アドベントカレンダー11日目の記事です。

はじめに

週末・深夜Pythonistaです。仕事でデータまとめるときにpandas,numpy,scipyあたりをちょこちょこする程度の人です。
趣味でDjangoを使うにあたって静的ファイルでよく躓いたので、『わからないところがわからない人』向けに、その躓きポイントと解決法をまとめられたらなと思います。
間違っている点があればご指摘いただけるとありがたいです。

ぶっちゃけると公式ドキュメントがとてつもなく親切なので、
しっかり読んで、理解して、その通り手を動かせば必ず理解しているはずの内容なのです。

Q1:そもそも静的ファイルis何?

静的ファイル?:thinking:
動かないファイルって何だよ・・・?

A1: Webの仕組みを知りましょう。

公式ドキュメント読みましょう
ものすごく乱暴に言うとCSS,JS等です。
Djangoが生成する動的なコンテンツに対して、サーバーから何も加工を加えずクライアント側に提供するファイルのことを指します。

Webサーバの仕組み:MDNリファレンス

静的ファイルという言い方自体はDjango独自の言い回し(?)のようですが、ウェブアプリケーションの仕組みがわかっていれば難なく理解できるものだと思います。

これがわからないあなた(過去の自分)はWebの仕組みを勉強しましょう。

静的ファイル (画像、JavaScript、CSS など) を管理する¶
ウェブサイトではふつう、画像や JavaScript、CSS などの追加のファイルを配信する必要があります。Django では、こうしたファイルのことを「静的ファイル (static files)」と呼んでいます。静的ファイルの管理を簡単にするために、Django は django.contrib.staticfiles を提供しています。
(Django公式ドキュメント)

ちゃんと公式ドキュメントに書いてありますね。。。

Q2:結局静的ファイルはどこに置くのが正解?

Django公式ドキュメント付属のチュートリアル・DjangoGirlsTutorial(有名なDjangoのチュートリアル)では、staticディレクトリをアプリ内に掘るように紹介されています。

polls ディレクトリの中に、 static ディレクトリを作成します。Django はそこから静的ファイルを探します。

(Django公式ドキュメント)

上記のpollsはアプリ名なので、アプリ内にディレクトリを作成するように指示しています。

静的ファイルはプロジェクトのどこに置けばいいの?
Djangoは、ビルトインの "admin" アプリにより、静的ファイルをどこで探せばいいのかわかっています。私たちがやることは、blog アプリのための静的ファイルを追加することだけです。

そのために、blogアプリの中に static というフォルダを作ります

djangogirls
├── blog
│ ├── migrations
│ ├── static
│ └── templates
└── mysite
(DjangoGirlsTutorial)

こちらも同様にアプリ内にディレクトリを作成しています。

一方で、ベストプラクティスではプロジェクト直下にstaticフォルダを置くことを推奨しています。

A2: どっちでもいい

アプリ内にstaticディレクトリを作成する場合も、以下のような構成にして名前空間を利用することがほとんどだからです。(※名前空間を使わない場合のまずい場合を番外編でご紹介します)
強いて言えば、散らばっていると個人的に見にくいので、ベストプラクティスを踏襲することをお勧めします。
併用は避けましょう。

(アプリ名) 
 ┣static
     └ (アプリ名)
           ┣css
           ┣js

Q3:本番環境css当たらない問題

Djangoアプリをデプロイしようとする者の心を折るものそれが静的ファイルだと信じています。。。

A3:Webサーバーの設定の見直しをしましょう

本番環境で静的ファイルの提供を行うのはWebサーバー側が担うことが多いと思います。
静的ファイルが一か所にまとめられているか?正しく配信の設定がなされているかを確認しましょう。

Django側で気を付けるべきは1か所に静的ファイルをまとめられているかどうかに尽きます。
静的ファイルを1か所にまとめるコマンドはpython manage.py collectstaticです。

このコマンドを叩くと開発環境下でdjango.contrib.staticfilesから見えている静的ファイルをsettings.pyで設定したSTATIC_ROOTに集められます。

しかもすでに同名のファイルがある場合はタイムスタンプを確認して新しいものを保存してくれるスグレモノ!
これを行うことで、Nginxなどの設定が楽になります。

このcollectstaticコマンド周りの公式ドキュメントは日本語化が進んでおらず英語のままなのも、公式ドキュメント斜め読み勢を陥れる一端となっているのかもしれません。。。

番外編:静的ファイルの衝突

アプリ内にstaticディレクトリを作成する場合、静的ファイルの名前の衝突に注意が必要です。
Djangoでは名前が同じ静的ファイルが存在する場合、最初に検索に引っかかった方のファイルのみ提供されます。

以下のように同じ名前の静的ファイルが存在する場合、Django側ではどちらの静的ファイルを提供すべきかがわかりません。
この場合saticfilesが見つけた最初のcssを当てることになります。

project_root/app1/static/main.css
h1{
  color: blue;
}
project_root/app2/static/main.css
h1{
  color: red;
}

なので、それぞれのテンプレートで以下のようにmain.cssを呼ぼうとしても...

project_root/app1/templates/app1.html
{% load static %}
<link rel="stylesheet" href="{% static "main.css" %}">
{% static "main.css" %}  <!-- デバッグ用 -->
<h1>App1.html<h1>
project_root/app2/templates/app2.html
{% load static %}
<link rel="stylesheet" href="{% static "main.css" %}">
{% static "main.css" %}  <!-- デバッグ用 -->
<h1>App2.html<h1>

このように片方のCSSしか当たりません。
App1はテキスト青色、App2はテキスト赤色にしたかったのに両方青色になってしまいます
app1_app2.PNG

こんな時はfindstaticコマンドを叩きましょう。
このコマンドを使うことで、Djangoの検索順がわかります。

$ python manage.py findstatic main.css
Found 'main.css' here:
  C:\Users\Cuz\Desktop\Projects\static_files_sample\app1\static\main.css
  C:\Users\Cuz\Desktop\Projects\static_files_sample\app2\static\main.css

この時上のapp1のcssが当たり、app2のcssが当たっていないことがわかると思います。

これもまた公式ドキュメントに書いてあります

まとめ

公式ドキュメント読みましょう!読もう!いや読むんだ!
これに尽きますね。。。
改めて読むととても細かいところまで書いているので、きちんと隅から隅まで丁寧に読むことをお勧めします。

(メディアファイル関連やデプロイまでは時間が足りませんでした。。。来年こそ計画的に書くぞっ!)

明日は@skd_nwさんの『開発で使ったライブラリの話とか』です!
よろしくお願いいたします!

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

Cisco IOS-XEのACLをRESTCONFで操作する(Python編)

はじめに

モデル駆動型プログラマビリティで使用されるトランスポートプロトコル(NETCONF、RESTCONF、gRPC)の内、RESTCONFを使ってCisco IOS-XEのACL設定を行った時のメモです。
操作ツールとしては、Postman、Python、Ansible等がありますが、今回はPythonを使い、名前付き拡張ACLに対してCRUD(作成、取得、更新、削除)を行いました。

1. RESTCONF概要

RESTCONFは、RESTFulインターフェースを提供するHTTP(S)ベースのプロトコルです。データ形式として、XMLに加えJSON形式もサポートしています(NETCONFはSSHベースでXML形式のみ)。

RESTCONF/NETCONFのCRUD操作

RESTCONF NETCONF
GET <get>, <get-config>
POST <edit-config> (operation="create")
PUT <edit-config> (operation="create/replace")
PATCH <edit-config> (operation="merge")
DELETE <edit-config> (operation="delete")

IOSはもともとREST APIもサポートしていますが、REST APIがベンダー固有の実装なのに対し、RESTCONFはRFC8040で標準化されたプロトコルであり、別物になります。

RESTCONFを含むモデル駆動型プログラマビリティの概要は、DevNet Learning Labsの以下セッションで分かり易くまとめられています。
モデル駆動型プログラマビリティの紹介

2. 用意した環境

こちらの記事と同様、Cisco dCloud環境を利用させて頂きました。
対象機器は「CSR1000v with IOS XE 16.08」、クライアントのPythonバージョンは「3.6.8」を使用しました。

3. ACL作成

まず、ACL設定が何も無い状態から、ACL名TESTを1行追加します。

3-1. Pythonコード

URLhttps://[IPアドレス]:[ポート番号]/restconf/data/Cisco-IOS-XE-native:native/ip/access-listに対し、PUTメソッドでリクエストを行っています。Body内のデータはJSON形式で定義しています。
ちなみにPOSTメソッドでは作成できませんでした。

create_acl.py
#!/usr/bin/python
import requests
import json

# disable warnings from SSL/TLS certificates
requests.packages.urllib3.disable_warnings()

# credentials for CSR1000v
HOST = '[IPアドレス]'
PORT = 443  # 環境によって異なる
USER = '[ユーザ名]'
PASS = '[パスワード]'


def main():
    # url string to issue request
    url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native/ip/access-list".format(h=HOST, p=PORT)

    # RESTCONF media types for REST API headers
    headers = {'Content-Type': 'application/yang-data+json',
               'Accept': 'application/yang-data+json'}

    # RESTCONF doby for new ACL
    body_data = {
                    "Cisco-IOS-XE-native:access-list": {
                        "Cisco-IOS-XE-acl:extended": [
                            {
                                "name": "TEST",
                                "access-list-seq-rule": [
                                    {
                                        "sequence": "30",
                                        "ace-rule": {
                                            "action": "permit",
                                            "protocol": "ip",
                                            "ipv4-address": "192.168.4.0",
                                            "mask": "0.0.0.255",
                                            "dest-ipv4-address": "192.168.100.0",
                                            "dest-mask": "0.0.0.255"
                                            }
                                        }
                                    ]
                                }
                            ]
                        }
                    }

    # this statement performs a PUT on the specified url
    response = requests.put(url, auth=(USER, PASS),
                            headers=headers, data=json.dumps(body_data), verify=False)

    # print the json that is returned
    print(response)

if __name__ == '__main__':
    main()

3-2. 実行結果

Status Code 204 (No Content)が返ってきました。

$ python create_acl.py
<Response [204]>

Configを見ると、想定通りのACLが作成されていました。

csr1#sh run | begin TEST
ip access-list extended TEST
 permit ip 192.168.4.0 0.0.0.255 192.168.100.0 0.0.0.255

4. ACL取得

次に、作成したACLをRESTCONFで情報取得してみます。

4-1. Pythonコード(1) IP設定取得

main()関数以外は3.と同様のため、これ以降は抜粋して記載します。
まず、URLhttps://[IPアドレス]:[ポート番号]/restconf/data/Cisco-IOS-XE-native:native/ipに対し、IP関連設定の情報をGETメソッドで取得してみました。

get_ip_info.py
def main():
    # url string to issue request
    url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native/ip".format(h=HOST, p=PORT)

    # RESTCONF media types for REST API headers
    headers = {'Content-Type': 'application/yang-data+json',
               'Accept': 'application/yang-data+json'}

    # this statement performs a GET on the specified url
    response = requests.get(url, auth=(USER, PASS),
                            headers=headers, verify=False)

    # print the json that is returned
    print(response.text)

4-2. 実行結果(1) IP設定取得

途中の"access-list": { "Cisco-IOS-XE-acl:extended": [ ~ ] }で、設定した内容を確認できました。
ちなみに、ACL設定前はこの"access-list"キーを含めて作成されていませんでした。そのため、3.の作成時、下の階層の"Cisco-IOS-XE-acl:extended": [ ~ ]をいきなりPUTしてもエラーになりました。

$ python get_ip_info.py
{
  "Cisco-IOS-XE-native:ip": {
    "domain": {
      "name": "demo.dcloud.cisco.com"
    },
    "forward-protocol": {
      "protocol": "nd"
    },
    "route": {
      "ip-route-interface-forwarding-list": [
        {
          "prefix": "0.0.0.0",
          "mask": "0.0.0.0",
          "fwd-list": [
            {
              "fwd": "GigabitEthernet1",
              "interface-next-hop": [
                {
                  "ip-address": "198.18.128.1"
                }
              ]
            }
          ]
        }
      ]
    },
    "ssh": {
      "rsa": {
        "keypair-name": "ssh-key"
      },
      "version": 2
    },
    "access-list": {
      "Cisco-IOS-XE-acl:extended": [
        {
          "name": "TEST",
          "access-list-seq-rule": [
            {
              "sequence": "30",
              "ace-rule": {
                "action": "permit",
                "protocol": "ip",
                "ipv4-address": "192.168.4.0",
                "mask": "0.0.0.255",
                "dest-ipv4-address": "192.168.100.0",
                "dest-mask": "0.0.0.255"
              }
            }
          ]
        }
      ]
    },
    "Cisco-IOS-XE-http:http": {
      "authentication": {
        "local": [null]
      },
      "server": true,
      "secure-server": true
    }
  }
}

4-3. Pythonコード(2) 拡張ACL設定取得

こちらは、より深い階層のURLhttps://[IPアドレス]:[ポート番号]/restconf/data/Cisco-IOS-XE-native:native/ip/access-list/extendedで、拡張ACLのみをピンポイントで取得する例です。

get_acl.py
def main():
    # url string to issue request
    url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native/ip/access-list/extended".format(h=HOST, p=PORT)

    # RESTCONF media types for REST API headers
    headers = {'Content-Type': 'application/yang-data+json',
               'Accept': 'application/yang-data+json'}

    # this statement performs a GET on the specified url
    response = requests.get(url, auth=(USER, PASS),
                            headers=headers, verify=False)

    # print the json that is returned
    print(response.text)

4-4. 実行結果(2) 拡張ACL設定取得

$ python get_acl.py
{
  "Cisco-IOS-XE-acl:extended": [
    {
      "name": "TEST",
      "access-list-seq-rule": [
        {
          "sequence": "30",
          "ace-rule": {
            "action": "permit",
            "protocol": "ip",
            "ipv4-address": "192.168.4.0",
            "mask": "0.0.0.255",
            "dest-ipv4-address": "192.168.100.0",
            "dest-mask": "0.0.0.255"
          }
        }
      ]
    }
  ]
}

5. ACLマージ

既存ACLに対し、エントリ(ACE)追加と、別のACLを追加する例をご紹介します。

5-1. Pythonコード(1) エントリ追加

既存のACL名TESTの2行目に、エントリを追加してみます。先ほどのシーケンス番号30に対し、今回は50を指定しています。ちなみにシーケンス番号を指定しない場合、エラーで設定できませんでした。
マージ用のメソッドであるPATCHを使い、既存設定はそのままで、Body内の内容を追加している形です。

merge_ace.py
def main():
    # url string to issue request
    url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native/ip/access-list".format(h=HOST, p=PORT)

    # RESTCONF media types for REST API headers
    headers = {'Content-Type': 'application/yang-data+json',
               'Accept': 'application/yang-data+json'}

    # RESTCONF doby for new ACL
    body_data = {
                    "Cisco-IOS-XE-native:access-list": {
                        "Cisco-IOS-XE-acl:extended": [
                            {
                                "name": "TEST",
                                "access-list-seq-rule": [
                                    {
                                        "sequence": "50",
                                        "ace-rule": {
                                            "action": "permit",
                                            "protocol": "ip",
                                            "ipv4-address": "192.168.5.0",
                                            "mask": "0.0.0.255",
                                            "dest-ipv4-address": "192.168.100.0",
                                            "dest-mask": "0.0.0.255"
                                            }
                                        }
                                    ]
                                }
                            ]
                        }
                    }

    # this statement performs a PUT on the specified url
    response = requests.patch(url, auth=(USER, PASS),
                            headers=headers, data=json.dumps(body_data), verify=False)

    # print the json that is returned
    print(response)

5-2. 実行結果(1) エントリ追加

Status Code 204 (No Content)が返ってきました。

$ python merge_ace.py
<Response [204]>

Configを見ると、想定通り2行目に追加されていました。

csr1#sh run | begin TEST
ip access-list extended TEST
 permit ip 192.168.4.0 0.0.0.255 192.168.100.0 0.0.0.255
 permit ip 192.168.5.0 0.0.0.255 192.168.100.0 0.0.0.255

5-3. Pythonコード(2) 別のACL追加

今度は、別のACL名TEST2を1行追加してみます。

merge_another_acl.py
def main():
    # url string to issue request
    url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native/ip/access-list".format(h=HOST, p=PORT)

    # RESTCONF media types for REST API headers
    headers = {'Content-Type': 'application/yang-data+json',
               'Accept': 'application/yang-data+json'}

    # RESTCONF doby for new ACL
    body_data = {
                    "Cisco-IOS-XE-native:access-list": {
                        "Cisco-IOS-XE-acl:extended": [
                            {
                                "name": "TEST2",
                                "access-list-seq-rule": [
                                    {
                                        "sequence": "70",
                                        "ace-rule": {
                                            "action": "permit",
                                            "protocol": "ip",
                                            "ipv4-address": "192.168.7.0",
                                            "mask": "0.0.0.255",
                                            "dest-ipv4-address": "192.168.100.0",
                                            "dest-mask": "0.0.0.255"
                                            }
                                        }
                                    ]
                                }
                            ]
                        }
                    }

    # this statement performs a PUT on the specified url
    response = requests.patch(url, auth=(USER, PASS),
                            headers=headers, data=json.dumps(body_data), verify=False)

    # print the json that is returned
    print(response)

5-4. 実行結果(2) 別のACL追加

$ python merge_another_acl.py
<Response [204]>

ACL名TEST2が追加されている事が分かります。

csr1#sh run | begin TEST
ip access-list extended TEST
 permit ip 192.168.4.0 0.0.0.255 192.168.100.0 0.0.0.255
 permit ip 192.168.5.0 0.0.0.255 192.168.100.0 0.0.0.255
ip access-list extended TEST2
 permit ip 192.168.7.0 0.0.0.255 192.168.100.0 0.0.0.255

6. ACLリプレイス

今度は、既存ACLを上書きし、別のACL名TEST3を作成する例です。通常のACL作業では基本的に実施しない(意図せずやってしまうと大事故になる)ケースだと思います。ただ、ACL設定以外では、パラメーターシートを元に「~~の設定は〇〇であるべき」のように宣言型で定義できるので、既存設定削除などの煩わしさ無しで設定できるメリットはあると思います。

6-1. Pythonコード

メソッドは、新規作成時と同様PUTを用います。

replace_acl.py
def main():
    # url string to issue request
    url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native/ip/access-list".format(h=HOST, p=PORT)

    # RESTCONF media types for REST API headers
    headers = {'Content-Type': 'application/yang-data+json',
               'Accept': 'application/yang-data+json'}

    # RESTCONF doby for new ACL
    body_data = {
                    "Cisco-IOS-XE-native:access-list": {
                        "Cisco-IOS-XE-acl:extended": [
                            {
                                "name": "TEST3",
                                "access-list-seq-rule": [
                                    {
                                        "sequence": "90",
                                        "ace-rule": {
                                            "action": "permit",
                                            "protocol": "ip",
                                            "ipv4-address": "192.168.9.0",
                                            "mask": "0.0.0.255",
                                            "dest-ipv4-address": "192.168.100.0",
                                            "dest-mask": "0.0.0.255"
                                            }
                                        }
                                    ]
                                }
                            ]
                        }
                    }

    # this statement performs a PUT on the specified url
    response = requests.put(url, auth=(USER, PASS),
                            headers=headers, data=json.dumps(body_data), verify=False)

    # print the json that is returned
    print(response)

6-2. 出力結果

$ python replace_acl.py
<Response [204]>

見事に上書されました。

csr1#sh run | begin TEST
ip access-list extended TEST3
 permit ip 192.168.9.0 0.0.0.255 192.168.100.0 0.0.0.255

ちなみに、以下記事を書いた方によると、NX-OSの場合、PUTメソッドがRFC8040の仕様通りではなく、PATCHメソッドと同様マージになるケースもあるようです。
Exploring IOS-XE and NX-OS based RESTCONF Implementations with YANG and Openconfig

7. ACL削除

最後に、作成したACLの削除を行ってみます。
事前に「3.ACL作成」と「5.ACLマージ」を実行し、TEST3が上書きされ、TESTTEST2が設定された状態にしておきます。

csr1#sh run | begin TEST
ip access-list extended TEST
 permit ip 192.168.4.0 0.0.0.255 192.168.100.0 0.0.0.255
 permit ip 192.168.5.0 0.0.0.255 192.168.100.0 0.0.0.255
ip access-list extended TEST2
 permit ip 192.168.7.0 0.0.0.255 192.168.100.0 0.0.0.255

7-1. Pythonコード

URLhttps://[IPアドレス]:[ポート番号]/restconf/data/Cisco-IOS-XE-native:native/ip/access-list/extended=TEST2に対し、DELETEメソッドでリクエストを行います。
URLの末尾に/extended=TEST2を指定することで、TEST2をACL単位で削除できます。
(エントリ単位での削除方法はちょっと分かりませんでした。。)

delete_acl.py
def main():
    # url string to issue request
    url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native/ip/access-list/extended=TEST2".format(h=HOST, p=PORT)

    # RESTCONF media types for REST API headers
    headers = {'Content-Type': 'application/yang-data+json',
               'Accept': 'application/yang-data+json'}

    # this statement performs a DELETE on the specified url
    response = requests.delete(url, auth=(USER, PASS),
                            headers=headers, verify=False)

    # print the json that is returned
    print(response)

7-2. 実行結果

$ python delete_acl.py
<Response [204]>

TEST2が削除されました。

csr1#sh run | begin TEST
ip access-list extended TEST
 permit ip 192.168.4.0 0.0.0.255 192.168.100.0 0.0.0.255
 permit ip 192.168.5.0 0.0.0.255 192.168.100.0 0.0.0.255

最後に

まだインターネット上にサンプルコードが少なかったので、お試しでやってみました。以前の記事で、要件資料と複雑なJinja2テンプレートを組み合わせてACL Configの生成を行いましたが、RESTCONFであれば、要件資料のようなパラメーター情報をJSON形式に変換すれば設定ができるため、自動化と相性が良いと思います。
今回はPythonの例でしたが、Ansibleのrestconf_getrestconf_configモジュールを使った例や、設定項目毎のモデル(URL)の確認・作成方法もまとめてみたいと思います。

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

Power BI で表現可能なR, Pythonの地理情報可視化

本記事は@yugoes1021王子による Power BI Advent Calendar 2019に参加しています。

もうネタが無いので

Power BIで地理関連の記事を各種書いてきましたが、単に地図上に可視化するという観点では、もうあまりネタが無いので、どうしても重箱の隅をつつくような記事になりがちです。(とはいえ、Web版とかEmbeddedとかを触るリソース、時間がない orz)

なので、残る可能性としてはPower BI Desktop上でRやPythonを使った地図だろうということに落ち着きました。
R, Pythonでの拡張は各所で紹介されているので、ここではオフィシャルのリンクだけにしておきます。
前と同じUberのオープンデータを使っての評価をしています。サンフランシスコのタクシープローブデータです。

R を使用した Power BI ビジュアルの作成
Power BI Desktop で Python スクリプトを実行する

R

Rの方がバリエーションがあります。Power BIではPythonより歴史があります。
若干躓きやすいのが、PowerBIが使うRのバージョン、インストール場所です。
以下のオプションページで設定できますので、自分の使いたい、いつも使っているRインタープリタを指定しましょう。
そうすることで、libraryのインストールの手間が省けます。
ただし、同じインタープリタでも環境をユーザーフォルダに保存する場合もあるので、その場合はグローバルなインタープリタ環境でインストールする必要があるでしょう。

image.png

library(maps)

古めのライブラリです。基本的には各種の白地図を表示して、その上にデータを表示するものです。
(コード中のggmapはバウンディングボックスを得る便利関数のためだけに使います)
with関数でポイントを重畳できます。

library(maps)
library(ggmap)
sbbox <- make_bbox(lon = dataset$longitude, lat = dataset$latitude, f = 0)
map('usa', col = "grey", fill = TRUE, bg = "white", border = 0, 
  xlim = c(sbbox[1], sbbox[3]), ylim = c(sbbox[2], sbbox[4]))
with(dataset, points(longitude, latitude, pch = 1, col = 'blue', cex = .2))

image.png

library(sf)

空間データを適切に扱うためのライブラリです。一度sf形式のデータフレームに変換する必要があります。
なんとデータフレームを直接plotできます。

library(sf)
library(sp)
dfsf <- dataset %>% st_as_sf(coords = c('longitude', 'latitude'), crs = 4236)
plot(dfsf, col = "blue", pch = 21)

image.png

library(tmap)

比較的容易にいろいろな主題図を描くことができるライブラリです。
通常のplotモードとLeafletビューアが立ち上がるviewモードを切り替えられ、便利です。
ですが、以下をみてわかるように、viewモードではベースマップが貼り付けられません。残念。

library(tmap)
library(dplyr)
library(sf)
library(sp)
dfsf <- dataset %>% st_as_sf(coords = c('longitude', 'latitude'), crs = 4236)
tmap_mode("plot")
map <- tm_shape(dfsf, name = "uber") +
    tm_symbols(shape = 21, col = "blue", size = 0.05) +
    tm_basemap("Stamen.Watercolor")
map

image.png

library(ggplot2)

ggplotにマップを描く機能が統合されています。おそらく、一般的なデータ処理を行う人は、通常はこれを使うのが一番しっくりくるのではないでしょうか?

library(ggplot2)
library(mapproj)
library(ggmap)
sbbox <- make_bbox(lon = dataset$longitude, lat = dataset$latitude, f = 0)
usmap <- map_data("state") 
ggplot() +
    geom_polygon(data = usmap, aes(x = long, y = lat, group = group), fill = "grey", alpha = 0.5) +
    geom_point(data = dataset, aes(x = longitude, y = latitude)) +
    theme_void() + coord_map(xlim = c(sbbox[1], sbbox[3]), ylim = c(sbbox[2], sbbox[4]))

image.png

library(ggmap)

やはり背景地図をもっと細かいものが欲しいとなると、これです。
どうもGoogle Maps APIの制限がきつくなったせいか、API Keyの登録が必要です。
また、便利な登録用関数がある以下の開発版を入手するようにしましょう。

自分のR環境で以下の方法で最新版をインストールすると、register_googleというキー設定が可能な関数が入ってきますので、アップグレードしておきます。

devtools::install_github("dkahle/ggmap")
library(ggplot2)
library(mapproj)
library(ggmap)
register_google(key = "YOUR_API_KEY")
sbbox <- make_bbox(lon = dataset$longitude, lat = dataset$latitude, f = 0)
map <- get_stamenmap(bbox = sbbox, zoom = 13, maptype = "toner-lite")
ggmap(map) +
    geom_point(aes(x = longitude, y = latitude), color = "blue" ,data = dataset, alpha = .5)

image.png

Python

Folium, ShapelyなどPythonには本格的な地図系可視化ライブラリや、非常に扱いやすいgeo pandasなど地理データ処理ライブラリがそろっているのですが、Power BI上で試そうとしたところ、なかなか動いてくれませんでした。
同じくFoliumを動かそうという人がいたのですが、以下のように、そもそも現状のPower BIでは限られたライブラリしか動かないらしく、素直にあきらめることにしました。。。

Help to implement Python Script - Microsoft Power BI Community

The following Python packages (non-Intel MKL) are currently supported for use in your Power BI reports. Reference: Python packages and versions

  • Matplotlib
  • numpy
  • pandas
  • scikit-learn
  • scipy
  • seaborn
  • statsmodels

Pythonもインタープリタを以下で設定します。Anacondaになると思いますが、たとえ新しいライブラリをインストールしても、Power BIでは使えないので、ご了承ください。

image.png

Matplotlib

その中でもMatplotlibにはmpl_toolkits:basemapというライブラリが存在するようです。
Matplotlib標準ではなく、インストールする必要があります。現在はpipインストールはサポートしておらずcondaなどを使います。

conda install -c anaconda basemap

でインストールするとAnaconda環境で使えるようになりました。

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
m = Basemap(llcrnrlon=BBox[0],llcrnrlat=BBox[2],urcrnrlon=BBox[1],urcrnrlat=BBox[3])
m.drawcoastlines()
x, y = m(dataset.longitude, dataset.latitude)
m.plot(x, y, 'o')
plt.show()

VSCodeでの結果:
image.png

が、そもそもMatplotlib以外のライブラリだめなので、PowerBIでは動きませんでした。orz

恒例の性能評価

以前と同じデータを使っているので、標準ライブラリとの比較をしてみましょう。
クエリエディタであらかじめレコード数を絞れるようにして試しました。
Pythonはお茶を濁して単に2次元グラフを表示しています。

1,000レコード

標準マップ含めて、問題なく表示されます。あくまで表示された数の話ですが。
image.png

10,000レコード

標準マップはすべての点が表示されない旨のメッセージが出ます。
他も見た感じ、大きな欠落は無い模様です。スピードについてもどれもそれほど変わりません。

image.png

100,000レコード

ArcGISはかけ始めました。標準マップはどうもランダムサンプリングをするようになっており、見た目の範囲はそれほど変わっていません。
他のライブラリは、PowerBIで動いているとは知らず、全部表示できているようです。(ほんと?)
tmapとggmapが少し遅いかなという以外は、それほど変わりません。1分も待たされることはありません。

image.png

1,000,000レコード

ここまでくると、Rビジュアルについても、データの間引きが行われているようです。
また、Uberデータの中に、ラスベガスまで行っている車があるので、全体を表示するのにggmapは時間がかかります(地図の拡大率の調整が必要)

image.png

まとめ

このような単純なマップではわざわざRのコードを使って可視化する意味は薄いのですが、特殊な描画や演算が必要な場合は、Rでしっかりとライブラリ化したものを埋め込んで使うようにすれば、出番もあるのではと思いました。

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

Pythonでアルゴリズム(ABC 146 C 二分探索

今日からatcoderをpythonで解いていきます。

https://atcoder.jp/contests/abc146/tasks/abc146_c
 
この問題は二分探索の最も典型的な問題です。
二分探索、理論はシンプルですが実装すると境界でWAしたりしなかったりしてしまいますよね。
かといって最後の2つに絞れたら2つとも試してみて答える、なんて泥臭いこともしたくないですww

今回は、左側から詰めていく二分探索(ある数を超えないぎりぎりの数を探すタイプの二分探索です)

l = 0
r = 1000000001

a, b, x = tuple(map(int, input().split(" ")))
while l < r - 1:
  m = l + (r - l) // 2
  p = m * a + b * len(str(m))
  if p > x:
    r = m
  else:
    l = m

print(l)
#l = left, m = middle, r = right

境界でバグらせないための今回のポイントは

  if p > x:
    r = m
  else:
    l = m

の部分です。
なんでここがポイントなの!?

それは、l + (r - l) // 2
ここを見てください。
書き換えると、(l + r) // 2
の部分ですね。(桁数がオーバーしないように。PYTHONなら不要という説をよく聞きます)

この値って、//で割られているのl+rが奇数のときは10.5とか1000.5とか、floatだと端数が出てきててこれを切り捨てています。
つまり、mはキモチ左によっている

これを読んで「いい加減すぎる!ふざけるな!!」と怒る人がいるかもしれません。

そういう人たちには声を大にして言いたい。

すみませんでした

超えちゃいけないから恐る恐る左から寄ってくる感じにしたらいいんじゃないかな
くらいの発送です。

ではでは

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

2Dデータ(DXF)に適当な高さ情報を加えて3Dデータ(STL)を出力

open3dとdxfgrabberを使えばよい.

dxf2stl.py
import open3d as o3d
import dxfgrabber

# DXFを読み込み
dxf = dxfgrabber.readfile("test.dxf")
# 実線の円だけ選択
c_cirs = [e for e in dxf.entities if e.dxftype == 'CIRCLE' and e.linetype == 'CONTINUOUS']

mesh = None
H = 50.0 # 円筒の高さ
Z = 0.0  # 円筒を並べるZ値
for c in c_cirs:
    m = o3d.geometry.TriangleMesh.create_cylinder(radius=c.radius, height=H)
    m.translate([c.center[0], c.center[1], Z])
    if mesh is None:
        mesh = m
    else:
        mesh += m
mesh.compute_vertex_normals()

# STLで出力
o3d.io.write_triangle_mesh("sample.stl", mesh)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

2Dデータ(DXF)に適当な高さ情報を与えて3Dデータ(STL)を出力

open3dとdxfgrabberを使えばよい.

dxf2stl.py
import open3d as o3d
import dxfgrabber

# DXFを読み込み
dxf = dxfgrabber.readfile("test.dxf")
# 実線の円だけ選択
c_cirs = [e for e in dxf.entities if e.dxftype == 'CIRCLE' and e.linetype == 'CONTINUOUS']

# 立体化
mesh = None
H = 50.0 # 円筒の高さ
Z = 0.0  # 円筒を並べるZ値
for c in c_cirs:
    m = o3d.geometry.TriangleMesh.create_cylinder(radius=c.radius, height=H)
    m.translate([c.center[0], c.center[1], Z])
    if mesh is None:
        mesh = m
    else:
        mesh += m
mesh.compute_vertex_normals()

# STLで出力
o3d.io.write_triangle_mesh("sample.stl", mesh)

色とかレイヤー毎に高さ決めたDXF描いて,自動で立体化したい.

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

弱い紐帯の強さとコミュニティ

この記事は静岡アドベントカレンダー13日目の記事です。

はじめに

朝ツイッター見てたら私の知っている静岡中部のIT勉強会というエントリーがドンブラコと流れてきて、うっかり拾ってしまい、つい埋めねばならない気がしたので、今回書くことにしました。ちなみに言語戦争の歴史は私の駄スライドなので、その当時の事はそっちを見てください。

私自身はShizuoka.pyの主催をやっていますが、最近は頻度高くは開催していません。ガチのPythonistaが東京に流れちゃったりとか、忙しくなって参加しにくくなったりとか色々あって参加者が減ってしまった、、、かといって初心者対応は静岡読書会で疲れちゃったしなーっていうのが主な理由です。

最近の静岡のPython事情については20日目に載るようなので今回は特にここでは触れません。

弱い紐帯の強さとプログラミングコミュニティ

弱い紐帯の強さという言葉をご存知でしょうか?要するに社会的なつながりが強いほど、保持している情報は類似してしまい、ある程度弱いつながりからのほうが新しい情報やアイデアがもたらされるという一見逆説的な理論です。

プログラミングコミュニティはまさに弱い紐帯を構成しているわけで、プログラミングという弱い共通項で多様な背景の人間が情報交換することで得られるものは大きいと思うし、皆さん恩恵を感じていると思います。そういう意味で、静岡に色々なコミュニティが形成されて多様なエコシステムが出来上がっている現状は望ましいことだと思います。

Mishima.sykという謎コミュニティ

静岡で開催されているコミュニティのなかでも特に秘密のヴェールに包まれている感があるのがMishima.sykでしょう。Mishima.sykは主にライフサイエンス業界の人が集まってPythonやRやJavascriptとか機械学習や深層学習の話をしています。静岡には製薬企業の研究所があったり、遺伝研があるので、そっち系の研究者がかなり集まっている関係でそういうコミュニティが形成されました。

資料や内容などもできるだけオープンにしているので、もし機械学習とかプログラミングをベースにしていて、バイオインフォマティクスとかケモインフォマティクスにも興味があるんだよねーという方がいましたら、気軽に参加してください。色々勉強になったり新しい発見があったりすると思うし、弱い紐帯の強さというものも実感できると思います。

静岡でバイオ・ケミストリ関連の学部に通っていて、Dryにも興味のある学生さんとか、バイオ系のDryの仕事をしてみたいなーというIT系の方も参加するとコネクションができていいことあるかもしれません。

Shizuoka.pyどうなってんの?

海外のPyConに遊びに行くのが忙しくてさぼってます。来年には開催しようと思っています。

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

Cifar10用DCGANモデルを晒すwith keras

概要

  • 時間がないので,GANの仕組みなどはとりあえず割愛
  • generatorとdiscriminatorの設定を書く
  • 味噌はgenerator,discriminatorともに活性化関数にLeakyReLuを使う
  • 学習プロセス全体のソースコードはgithubにアップロード予定です(準備中です申し訳ありません)

モデルパラメータ達

Generator

generator
def _build_generator(self) -> Model:
    start_pix_x = 4
    start_pix_y = 4
    kernel_ini = RandomNormal(mean=0.0, stddev=0.02)

    inputs = Input(shape=self.noise_shape)
    x = Dense(
        units=256*start_pix_x*start_pix_y,
        kernel_initializer=kernel_ini,
        bias_initializer='zeros')(inputs)
    x = LeakyReLU(alpha=0.2)(x)
    x = Reshape((start_pix_x, start_pix_y, 256))(x)
    x = Conv2DTranspose(
        filters=128,
        kernel_size=4,
        strides=2,
        padding='same',
        kernel_initializer=kernel_ini,
        bias_initializer='zeros')(x)
    x = LeakyReLU(alpha=0.2)(x)
    # x = BatchNormalization(axis=3)(x)
    x = Conv2DTranspose(
        filters=128,
        kernel_size=4,
        strides=2,
        padding='same',
        kernel_initializer=kernel_ini,
        bias_initializer='zeros')(x)
    x = LeakyReLU(alpha=0.2)(x)
    # x = BatchNormalization(axis=3)(x)
    x = Conv2DTranspose(
        filters=128,
        kernel_size=4,
        strides=2,
        padding='same',
        kernel_initializer=kernel_ini,
        bias_initializer='zeros')(x)
    x = LeakyReLU(alpha=0.2)(x)
    x = Conv2D(
        filters=3,
        kernel_size=3,
        padding='same',
        kernel_initializer=kernel_ini,
        bias_initializer='zeros')(x)

    y = Activation('tanh')(x)

    model = Model(inputs, y)
    if self.verbose:
        model.summary()

    return model

Discriminator

discriminator
def _build_discriminator(self) -> Model:
    kernel_ini = RandomNormal(mean=0.0, stddev=0.02)
    inputs = Input(shape=self.shape)
    x = GaussianNoise(stddev=0.05)(inputs)  # prevent d from overfitting.
    x = Conv2D(
        filters=64,
        kernel_size=3,
        padding='SAME',
        kernel_initializer=kernel_ini,
        bias_initializer='zeros')(x)
    x = LeakyReLU(alpha=0.2)(x)
    # x = Dropout(0.5)(x)
    x = Conv2D(
        filters=128,
        kernel_size=3,
        strides=2,
        padding='SAME',
        kernel_initializer=kernel_ini,
        bias_initializer='zeros')(x)
    x = LeakyReLU(alpha=0.2)(x)
    # x = Dropout(0.5)(x)
    # x = BatchNormalization(axis=3)(x)
    x = Conv2D(
        filters=128,
        kernel_size=3,
        strides=2,
        padding='SAME',
        kernel_initializer=kernel_ini,
        bias_initializer='zeros')(x)
    x = LeakyReLU(alpha=0.2)(x)
    # x = Dropout(0.5)(x)
    # x = BatchNormalization(axis=3)(x)
    x = Conv2D(
        filters=256,
        kernel_size=3,
        strides=2,
        padding='SAME',
        kernel_initializer=kernel_ini,
        bias_initializer='zeros')(x)
    x = LeakyReLU(alpha=0.2)(x)

    x = Flatten()(x)
    features = Dropout(0.4)(x)

    validity = Dense(1, activation='sigmoid')(features)

    model4d = Model(inputs, validity)
    model4g = Model(inputs, validity)
    if self.verbose:
        model4d.summary()

    return model4d, model4g

出力結果

出力結果の行はクラスに対応しています.
DCGANはただ画像を生成するだけですが,元の画像で構築した学習モデルに生成画像を入力して予測されたラベルによってラベリングして,予測クラスごとに画像を出力させています.
generatorにLeakyReLUをいれることで,物体の対象がよりしっかりと生成できてる感がある感じがします.
original2dcgan.png

結論

突貫でやったので,詳しく後日ちゃんと書きたいと思います.

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

DiscordAPIでラグナロクマスターズのチーム管理ツールを作りました

この記事はDiscord Advent Calendar 2019の11日目の記事です。

ごきげんよう?
DiscrodAPIを使ってラグナロクマスターズのチーム管理ツールを作成した @bboobbaa です。

当記事では、ツールのコンセプトや構成、運用について、サービス開発について大切だと感じたことをご紹介します!

ラグナロクマスターズとは

ラグナロクオンラインが2002年に公開されてから17年経つ2019年、スマホ対応のラグナロクマスターズ(以下ラグマス)がリリースされました!
ラグマストップ

昔は2Dだったキャラクターも3Dとなり、「ラグナロクをを知るものたちよ、今こそここに来たれ!」というゲームです。
まだ遊んだことがない方はラグマスで遊びましょう?‍♀️

ゲームコミュニケーションはDiscord時代

さてさて近年、ゲームのコミュニケーションやコミュニティ運用は、Discord全盛と言っても大袈裟ではないですよね。
Discordトップ

ラグマスでも多くのギルドがDiscordを利用しています。
こういった現状を踏まえ、Discordを使ったラグマスギルドをお手伝いするためのツールを作りました!

「カプラのラグマスギルド管理」公開

サービス「カプラのラグマスギルド管理」のコンセプトはギルドマスター支援としました。
ラグマスのギルドマスターを助けるサービスを目指しています。
カプラのラグマスギルド管理

なぜギルドマスターを助けるの

実は、ラグマスのギルドマスターはとても多忙なのです!

ラグマスのギルドマスター(以下ギルマス)は多くの仕事を抱えています。
一例を挙げると以下のようなものです。

  • 攻城戦メンバー出席管理
  • 攻城戦パーティー調整
  • メンバー勧誘やギルドPRなどの情報発信

これらは通常Discordや、グーグルスプレッドシート、Twitterなどで行われています。
ギルマスは単にゲームを遊ぶだけでなく、これら複数のツールを駆使しながらギルドメンバーが楽しく遊べる環境づくりに貢献しています。

image.png

実はギルマスを続けるのってすごく大変じゃないですか??
なんとかギルマスを助けなくちゃという気持ちでサービスを開発しました。

Discordのイベント出欠機能をリリース

2019年9月、「カプラのラグマスギルド管理」をリリースしました。

以下のような機能を備えています。

  • Discordボットからの対話コマンド機能
  • Discordからのメンバーインポート
  • アカウント登録機能
  • イベント出欠機能

とにかく公開したかったので最低限の機能という感じで公開しました。

サービスの売りとしては、ギルドメンバーが使っているDiscordからボット対話ができるのでスプレッドシートより学習コストが少ない点、アカウント機能があるためスプレッドシートの公開運用よりもセキュアであるところです。

構成

ユーザーは2箇所からサービスにアクセスすることができます。
Discordからはボット経由で、ウェブからはブラウザで「カプラのラグマスギルド管理」にアクセスできる仕組みです。
カプラのラグマスギルド管理の構成

Discordのボット側はdiscord.py、サーバーサイドはLaravelからMySQLをみています。
LaravelからDiscordを叩きたい場合は、RestCordを使用しました。

フロントはBootstrapです。

開発の感触

Discordボット開発では、discord.pyがすごくいいです。
discord.pyでなんでもできるので不自由しないはずです。

他にはLaravelの権限でGateが便利でした。権限追加がとてもやりやすいです。

リリース後の運用

リリース後は運用フェーズになります。
幸運にも初期からいくつかのラグマスのギルドにご利用いただけました。

初期からユーザーに使っていただいたことが、サービスが進化するきっかけとなりました。

リリース直後は問題山積

リリース日を急いだこともあり、リリース後は問題山積みでした。

  • バグ報告
  • ユーザーからの要望
  • 仕様変更部分の修正

リリース後すぐに「仕様をぼんやりしたたままリリース日のラインだけを引いた開発」、を進めたツケを払うことになりました。

テストできなかった箇所やリリース直前の仕様変更部分でのバグ対応や、ユーザーさんからもっとこうして欲しいという意見を次々といただき対応に追われました。

image.png

一時期は1日1件ペースで問い合わせをいただくこともありました。
サービスを公開していると、アプリケーションやインフラだけでなくUIもテキストなどの説明文も全てがユーザーのUXに関係してきます。十分な対応がない箇所は全てが脆弱性になる可能性があります。

しかし全ては身から出た錆であり、ギルマスとラグマスユーザーのためには対応しなければなりません。

ユーザーのおかげでサービスがどんどんよくなる

カプラのラグマスギルド管理」ではユーザーさんから要望をいただき新機能がどんどん追加されています。
最近では、ボットからDiscordのリアクションAPIを利用してPOSTできたりと少しずつバージョンアップを重ねています。

サービスが良くなってくると Twitter でご紹介いただけるようになった気がしています。

食器洗い乾燥機と言われました?‍♀️

正直な話、リリース時を振り返ると機能が少なく、わかりにくい箇所や使いにくいところばかりでした。
そんな不十分なツールでも使ってくださったユーザーのみなさんに本当に感謝しかないです。

この感謝を胸に、今後もギルマスを支援できるツールを目指し真剣に向き合っていきます。

最後はサービス開発について

結論からいえば、サービス開発で大事なことは2つあると思いました。

  • まずはリリースする
  • 問い合わせ窓口を用意する

まずリリースする

サービス開発では、「まずリリースしろ」とよく言われます。
これは本当にその通りだと感じました。

リリースするからことによって自分の考えが足りなかった箇所や思い込みに気づかされます。
サービスをリリースするだけで、多くのことを学ぶことができるからです。

問い合わせ窓口を用意する

あとはユーザーさんから連絡をもらうための問い合わせ窓口を設けることが大事です。窓口はTwitterでもなんでも大丈夫です。ユーザーさんから意見をいただければ励みになり、開発が進みます。

ユーザーさんが開発の後押しをしてくれるのです。

Discordボットいいですよ

この記事を読んで、Discordボットサービス開発に興味を持った方がいたら、ぜひリリースしてください。
ただし問い合わせ窓口は必ず用意しましょう。

P.S.
Discordはすごいサービスです。Qiitaをご覧のみなさんにはSlackやChatworkが有名かもしれませんが、Discordいいですよ。APIや権限周りは綺麗で使いやすいと感じました。

Discordボットを作ったことがない方がいたらぜひ試して欲しいと思います!

商標

© Gravity Co., Ltd. & Lee MyoungJin(studio DTDS). All rights reserved.
© GungHo Online Entertainment, Inc. All Rights Reserved.

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

DXFをpythonで扱う

dxfgrabberを使うとpythonでDXFを読めるらしい
pip install dxfgrabber

フリーの2DCAD jw_cadで適当なDXFファイルを作っておいて,読んでみる.

dxf.py
import dxfgrabber

dxf = dxfgrabber.readfile("test.dxf")
# ヘッダ
# print(dxf.header)

# 図形要素
# print(vars(dxf.entities))
cirs = [e for e in dxf.entities if e.dxftype == 'CIRCLE']
lines = [e for e in dxf.entities if e.dxftype == 'LINE']

# 円
print(vars(cirs[0]))
# {'dxftype': 'CIRCLE', 'handle': None, 'owner': None, 'paperspace': None, 'layer': '_0-0_', 'linetype': 'CONTINUOUS', 'thickness': 0.0, 'extrusion': (0.0, 0.0, 1.0), 'ltscale': 1.0, 'line_weight': 0, 'invisible': 0, 'color': 7, 'true_color': None, 'transparency': None, 'shadow_mode': None, 'layout_tab_name': None, 'center': (94.41901840490794, 356.85030674846627), 'radius': 16.151818630112004}
# centerが円中心のx, y
# radiusが円半径
# linetypeが線種

# 直線
print(vars(lines[0]))
#{'dxftype': 'LINE', 'handle': None, 'owner': None, 'paperspace': None, 'layer': '_0-0_', 'linetype': 'CONTINUOUS', 'thickness': 0.0, 'extrusion': None, 'ltscale': 1.0, 'line_weight': 0, 'invisible': 0, 'color': 7, 'true_color': None, 'transparency': None, 'shadow_mode': None, 'layout_tab_name': None, 'start': (386.4472392638037, 316.28711656441715), 'end': (386.4472392638037, 522.9865030674847)}
# startが始点x,y,endが終点x,y
# linetypeが線種

参考
https://qiita.com/ackermanrf128/items/d9275a7d077c1dff3ec7

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

DXFをpythonで読む

dxfgrabberを使うとpythonでDXFを読めるらしい
pip install dxfgrabber

フリーの2DCAD jw_cadで適当なDXFファイルを作っておいて,読んでみる.

dxf.py
import dxfgrabber

dxf = dxfgrabber.readfile("test.dxf")
# ヘッダ
print(dxf.header)
#{'$ACADVER': 'AC1009', '$DWGCODEPAGE': 'ANSI_1252', '$INSBASE': (0.0, 0.0, 0.0), '$EXTMIN': (0.0, 0.0), '$EXTMAX': (841.0, 594.0), '$LIMMIN': (0.0, 0.0), '$LIMMAX': (841.0, 594.0), '$LTSCALE': 1.0}

# 図形要素
# print(vars(dxf.entities))
cirs = [e for e in dxf.entities if e.dxftype == 'CIRCLE']
lines = [e for e in dxf.entities if e.dxftype == 'LINE']

# 円
print(vars(cirs[0]))
# {'dxftype': 'CIRCLE', 'handle': None, 'owner': None, 'paperspace': None, 'layer': '_0-0_', 'linetype': 'CONTINUOUS', 'thickness': 0.0, 'extrusion': (0.0, 0.0, 1.0), 'ltscale': 1.0, 'line_weight': 0, 'invisible': 0, 'color': 7, 'true_color': None, 'transparency': None, 'shadow_mode': None, 'layout_tab_name': None, 'center': (94.41901840490794, 356.85030674846627), 'radius': 16.151818630112004}
# centerが円中心のx, y
# radiusが円半径
# linetypeが線種

# 直線
print(vars(lines[0]))
#{'dxftype': 'LINE', 'handle': None, 'owner': None, 'paperspace': None, 'layer': '_0-0_', 'linetype': 'CONTINUOUS', 'thickness': 0.0, 'extrusion': None, 'ltscale': 1.0, 'line_weight': 0, 'invisible': 0, 'color': 7, 'true_color': None, 'transparency': None, 'shadow_mode': None, 'layout_tab_name': None, 'start': (386.4472392638037, 316.28711656441715), 'end': (386.4472392638037, 522.9865030674847)}
# startが始点x,y,endが終点x,y
# linetypeが線種

# 線種一覧(jw_cadの)
print(vars(dxf.linetypes))
#{'_table_entries': {'CONTINUOUS': <dxfgrabber.linetypes.Linetype object at 0x000002B43A5796A0>, 'DASHED1': <dxfgrabber.linetypes.Linetype object at 0x000002B43A5796D8>, 'DASHED2': <dxfgrabber.linetypes.Linetype object at 0x000002B43A579710>, 'DASHED3': <dxfgrabber.linetypes.Linetype object at 0x000002B43A579748>, 'CENTER1': <dxfgrabber.linetypes.Linetype object at ...

参考
https://qiita.com/ackermanrf128/items/d9275a7d077c1dff3ec7

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

お店を自由に追加してみよう!CreateView編

今回はCreateView編です。

今までTemplateViewを用いてListView、DetailViewを作成してまいりました。
その1-TemplateViewを使ってみよう!IndexView編
その2-TemplateViewをつかってみよう!Detailview編
お店の一覧、お店の詳細ページを表示ができるようになっている状態の続きと致します。

自分で好きなデータを追加できるって掲示板みたいで楽しいですよね!!

(見慣れない技術の解説)forms.pyでフォームの定義をおこなう

ここであらたなフォームという技術が現れました。
このforms機能はお仕事ではかなり使われることが多いので、慣れておくことを絶対にお勧めしています。
これは何なのかというと、pythonでこのform部分を作ってしまい、モデルと連動してhtmlの<form></form>を作成することになります。
最終的にwebサイトのフォーム部分を表示させることが目的となっていますので、怖がらずで大丈夫ですよ!

編集するファイルはこちら

mysite
│  db.sqlite3
│  manage.py
├─mysite
│  │  settings.py
│  │  urls.py
└─test_app
    │  admin.py
    │  apps.py
    │  models.py
    │  tests.py
    │  urls.py(ここをルーティング設定のために編集)
    │  views.py(ここをCreateView設定のために編集)
    │  forms.py(ここにお店追加の投稿フォームclassを作成)
    ├─migrations
    └─templates
       └─test_app
            └─list.html(ここにお店追加のリンクを表示)
               detail.html
               form.html(お店追加の投稿フォームを表示)

ルーティング設定

このURLでお店の作成ページにリンクできるように設定します。

http://localhost:8000/create

test_appのurls.pyにルーティングを設定します。

urls.py
from django.urls import path
from . import views

app_name = 'test_app'
urlpatterns = [
    path('index/',views.IndexView.as_view(),name='index'),
    path('<int:pk>/',views.DetailView.as_view(),name='detail'),
    path('create/',views.CreateView.as_view(),name='create'),#(ここを追加)
]

解説:app_nameとは、どのアプリ名に対してのルーティングファイルであるか指定する必要があります。ページ上のリンク(htmlで書かれたページのaタグで書かれたリンク)で<a href="{% url 'test_app:create' %}">と記述するため、test_appとアプリ名を設定する必要があります。
また、path('create/',views.CreateView.as_view(),name='create')ではcreate/のURLでviews.pyのCreateViewを呼び出しています。as_view()はテンプレートビューをビューとして扱うため使用されています。このas_view()を付けることによりビュー定義の記述がぐっと楽になります。テンプレートビュー限定のメリットですね。

CreateViewをviews.pyに設定

views.py
from django.views import generic
from .models import Shop
from .forms import ShopCreateForm #ここを追加

# Create your views here.
class IndexView(generic.ListView):
    model = Shop
    template_name = 'test_app/list.html'
class DetailView(generic.DetailView):
    model=Shop
    template_name = 'test_app/detail.html'

class CreateView(generic.CreateView):#ここ以降を追加
    model = Shop
    form_class = ShopCreateForm
    template_name = "test_app/form.html"
    success_url = "/index"

解説:form_classにforms.pyで投稿フォーム用に今回作成するフォームクラスであるShopCreateFormを指定しています。
ちゃんとimport ShopCreateFormを忘れずにお願いします。

views.pyとforms.pyを作成し、行ったり来たりするため、慣れるまで時間がかかりますが頑張りましょう!焦らず無理せず。
きっと慣れてさらに先に進めます!

お店投稿フォームをforms.pyに作成

forms.pyが作成されていない場合、新たに作成する場合があります。作成する場所はtest_appの直下です。views.pyやmodels.pyと同じレベルに作成します。
forms.pyはこのようになります。

forms.py
from django import forms
from .models import Shop

class ShopCreateForm(forms.ModelForm):
    class Meta:
        model = Shop
        fields = ("name","tell_num","address")

解説:forms.ModelFormとは、フォームを作成するためのdjangoの機能となっています。
モデルで定義されているフィールドをそのまま使います。
そのなかで、fieldsにてフォームで表示させるフィールドを選び、絞ることができます。

お店のデータを投稿したい場合、すでにShopのフィールドは作成されています。実際にmodels.pyの内容はこちらで、nametell_numaddresscreated_atなどフィールドが作成されています。

models.py
from django.db import models

# Create your models here.
class Shop(models.Model):
    #各フィールドの定義
    name = models.CharField('shopname',max_length=30)
    tell_num = models.CharField('tell_number',max_length=13)
    address = models.CharField('address',max_length=30)
    created_at = models.DateTimeField(auto_now_add=True)

    #admin画面の表示内容
    def __str__(self):
        return self.name

このShopクラスのフィールドをそのままforms.pyで使うことを意味しているのです。
ModelFormを用いると大変楽にフォームページを作成できるのです!

投稿ページのform.htmlを作成

mysite/test_app/templates/test_app/form.html
<!DOCTYPE>
<html>
    <head>
    </head>
    <body>
    <p>お店の作成</p>
        <form method="post">
            {% csrf_token %}
            {{ form.as_p }}
            <button type="submit">save</button>
        </form>
    </body>
</html>

解説:<form method="post">でフォームタグを作成しています。
{% csrf_token %}はフォームデータに改ざん防止の特殊な何かのデータを添えて送信するもので、必須です。
これを入れないと、djangoがエラーとなるため、必ず入れましょう。
{{ form.as_p }}はforms.pyで作成したShopCreateFormをviews.pyのform_classで指定していたため、html側ではformと記述するだけでフォームが呼び出せます。formと書く、と刷り込ませて良いです!
<button type="submit">save</button>ではデータ送信ボタンを作成しています。」

「お店の投稿」リンクを一覧ページに作成

test_app/templates/test_app/list.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
  <p>一覧の表示</p>
  <p><a href="{% url 'test_app:create' %}">お店の追加</a></p><!-- この行を追加 -->
  <table>
    <tr>
    </tr>
    {% for shop in shop_list %}
    <tr>
      <td>{{ shop.name }}</td>
      <td><a href="{% url 'test_app:detail' shop.pk %}">詳細</a></td>
    </tr>
    {% endfor %}
  </table>
  </body>
</html>

解説:お店の追加ページへリンクするためにリンクを書いています。パスはtest_appというアプリ名のcreateを指定しています。

formの表示の確認を行う

「お店の追加フォーム画面」で団子屋という名前のお店を追加してみます。
create画面.png

「リスト画面」で追加されたデータを確認してみましょう。
クリエイト後トップ画面.png

「団子屋詳細画面」で団子屋の詳細を確認してみましょう。
団子屋詳細ページ.png

お疲れさまでした✨

最後に

データ追加のための設定は必ずと言っていいほど重要なdjangoスキルとなるでしょう。CreateViewを用いてスピーディに作成ができることでかなりDjangoに慣れているといえると思います。
3つくらい自分でアプリを作成してみれば、データの追加、詳細、一覧が含まれるような、お店掲示板、日記などのアプリケーションが作成できるようになっていると思います。

わからないところや質問など受け付けておりますので、一緒に頑張ってゆきましょう!

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

AaaSからZaaSまで「as a Service」を探したら色々なサービスが見えた話

はじめに

近年IT界隈では、IaaS(Infrastructure as a Service)やPaaS(Platform as a Service)などの~~ as a Serviceという言葉をよく聞くと思います。
ここでは、それらをまとめて、 [A-Z]aaS と呼びたいと思います。
FirebaseなどのBaaS(Backend as a Service)やAWS LambdaなどのFaaS(Function as a Service)など色々な[A-Z]aaSを聞く機会が増えてきたんじゃないでしょうか。
今回は色々な[A-Z]aaSを探してみました。結果としては 910個 もの[A-Z]aaSを見つけることができました。(探した結果を全て、後半に表示してあります。)

[A-Z]aaSの探し方

最初の二文字を固定して、グーグル検索のサジェストに表示されるものを収集することにします。
英字が26文字なので、26*26=676パターンあります。
(注意) グーグル検索のサジェストに出てきただけで、実際にそういうサービスがあるとは限りません。

個人的に面白かった5選

ZaaS (Zangyou as a Service)

サービス残業のことです。まさかの日本語ですね。
(参考)https://monobook.org/wiki/ZaaS_(Zangyo_as_a_Service)

FaaS (Failure as a Service)

障害をシミュレートするサービスだそうです。
(参考)https://www.apriorit.com/dev-blog/567-failure-as-a-service
https://dev.classmethod.jp/testing/reinvent2018-gremlin/

SaaS (Service as a Service)

サービスとしてのサービスという名の通り、サービスを顧客にあったように、紹介するみたいなイメージでしょうか。
(参考)https://www.ft.com/content/f88bc87a-0e4b-11e2-8b92-00144feabdc0

CaaS (Crime as a Service)

他のサービスを攻撃するために作られたツールをサービスとして提供するというもののようです。危険ですね。。。
(参考)https://www.entrepreneur.com/article/298727

探す方法

(検討1) Google Suggest APIを使う

Google Suggest APIというものがあり、ある単語を打った時のグーグル検索のサジェストを取得することができます。(参考)https://so-zou.jp/web-app/tech/web-api/google/suggest/
しかし、[A-Z]aaSをサジェストから探すときには、普通にサジェストを得るのではなく、まず検索バーに「as a Service」を打っておき、その後に調べたい先頭の文字を打つ必要があります。そのためGoogle Suggest APIではどういったURLのページにリクエストを送ればいいのか分かりませんでした。。。これが可能であれば、収集のための作業量が10分の1になっていたと思います。

(検討2) python OCR を使う

サジェストされた結果を手で打っていくことだけは避けたかったため、OCRで画像情報を文字列に変換していくという方法にします。
chromeの検索バーにひたすらAa as a ServiceからZz as a Serviceまでを打ち、サジェストされている画面をスクリーンショットで取っていきます。(スクリーンショットを取ること676回)

以下のプログラムでOCRを使って、画像から文字情報を抜き出します。
そのあとに、[A-Z]aaSの形式に当てはまる形のものを、抜き出します。
以下のコードで、画像から[A-Z]aaSを抜き出すことができました。

main.py
from PIL import Image
import glob

import pyocr
import pyocr.builders

tools = pyocr.get_available_tools()
tool = tools[0]

langs = tool.get_available_languages()
lang = langs[0]

# a ~ zで回す
for i in range(97, 97+26):
    image_files = glob.glob("images/%c/Screen*"%chr(i))
    words = []
    for image_file in image_files:
        im = Image.open(image_file)
        croped_image = im.crop((300, 250, 1000, 730)) #基準

        # OCRで画像から文字を一行ずつ読み取る
        line_and_word_boxes = tool.image_to_string(
            croped_image,
            lang=lang,
            builder=pyocr.builders.LineBoxBuilder()
        )

        #得た文字列から一列ごとに読み取る
        for line_words in line_and_word_boxes:
            if line_words.content.endswith('as a service') and line_words.content.startswith(chr(i)):
                word = line_words.content.capitalize()
                word = word.replace('service', 'Service')
                print(word)
                words.append(word)

    #改行コードで連結して、ファイルに書き込む
    words = '\n'.join(list(set(words)))
    with open("result/"+chr(i)+".txt", 'w') as f:
        print(words, file=f)

A-Zまで

以下に取得した[A-Z]aasを全て掲載します。今回、調べることができたのは、910個と、とてつもない量です。スクロールするのもめんどくさいです。探したのは、グーグル検索のサジェストから得た結果だけなので、実際にはもっとあると思います。

AaaS

Apple as a Service
Auto as a Service
Aas as a Service
About platform as a Service
Availability as a Service
Agriculture as a Service
Application as a Service
Advisory as a Service
Api management as a Service
Aws platform as a Service
Agency as a Service
Aws kafka as a Service
Air conditioning as a Service
Agreement as a Service
Audit as a Service
Amazon kubernetes as a Service
Active directory as a Service
About rap as a Service
All as a Service
Azure app as a Service
Admin as a Service
As a Service
Afschepen as a Service
Advertising as a Service
Access management as a Service
Axa insurance as a Service
App as a Service
Android as a Service
Access as a Service
Azure platform as a Service
Airbnb as a Service
Aws blockchain as a Service
Adobe as a Service
Accounting as a Service
Akka as a Service
Azure desktop as a Service
Air as a Service
Agreement software as a Service
Aws infrastructure as a Service
Authentication as a Service
Appliance as a Service
About software as a Service
Amazon container as a Service
Adesso as a Service
Agent as a Service
Aem content as a Service
Airport as a Service
Alert as a Service
Apache as a Service
Aviation as a Service
Azure database as a Service
Amazon as a Service
Ansible as a Service
Automation as a Service
Amazon blockchain as a Service
Aem as a Service
About infrastructure as a Service
Azure infrastructure as a Service
Alexa as a Service
Ad as a Service
Avaya as a Service
Api as a Service
Avatar as a Service
Ai as a Service
And security as a Service
Av as a Service
Ad self Service plus as a Service
Assurance as a Service

BaaS

Branch as a Service
Book as a Service
Banking as a Service
Bi as a Service
Bginfo unable to launch as a Service
Backup as a Service
Bike as a Service
Bbva banking as a Service
Bicycle as a Service
Bss as a Service
Business process as a Service
Breakfast as a Service
Brand as a Service
Bd as a Service
Bginfo unable to relaunch as a Service
Big data as a Service
Battery as a Service
Bginfo as a Service
Billing as a Service
Bcp as a Service
Broker as a Service
Business as a Service
Bim as a Service
Building as a Service
Brain as a Service
Blockchain as a Service
Backend as a Service
Business model as a Service
Bem as a Service
Bgp as a Service
Business model software as a Service

CaaS

Cloud as a Service
Cfo as a Service
Classification as a Service
Customer experience as a Service
Cctv as a Service
Cyber threat intelligence as a Service
Cyber as a Service
Cyber range as a Service
Cat as a Service
Css as a Service
Car as a Service
Cybercrime as a Service
Chef as a Service
Customer Service as a Service
Cyber security as a Service
Client as a Service
Cnae software as a Service
Crime as a Service
Cdn as a Service
Credit as a Service
Community as a Service
Cdw device as a Service
Cntlm as a Service
Cto as a Service
Cloud computing as a Service
Ccm as a Service
Cts as a Service
Check-in as a Service
Channel as a Service
Certificate as a Service
Capital as a Service
Cdm as a Service
Chat as a Service
Crafting as a Service
Ckan as a Service

DaaS

DDos as a Service
Data as a Service
Dxc insurance as a Service
Data center as a Service
Drupal as a Service
Docker as a Service
Dhs workplace as a Service
Db as a Service
Django as a Service
Duo as a Service
Dpi as a Service
Dxc storage as a Service
Ddi as a Service
Dynamics as a Service
Devops as a Service
Db2 as a Service
Dxc digital insurance as a Service
Django platform as a Service
Database as a Service
Dpo as a Service
Dmp as a Service
Dmarc as a Service
Dsp as a Service
Data science as a Service
Dxc desktop as a Service
Docker swarm as a Service
Device as a Service
Dhcp as a Service
Dsi as a Service
Dba as a Service
Dxc backup as a Service
Dxc platform as a Service
Dr as a Service
Development as a Service
Dns as a Service
Docker start as a Service
Desktop as a Service
Drone as a Service
Dns server as a Service
Dc as a Service
Dropbox as a Service
Dxc device as a Service
Docker compose as a Service
Dpd sorry as a Service
Delivery as a Service
Domain as a Service
Dnssec as a Service
Dms as a Service
Dbaas database as a Service
Dmz as a Service
Drm as a Service
Driver as a Service

EaaS

Exchange as a Service
Etcd as a Service
Economy as a Service
Epc as a Service
Erp as a Service
Ea as a Service
Example of platform as a Service
Elasticsearch as a Service
Endpoint as a Service
Example of infrastructure as a Service
Email as a Service
Emotion as a Service
Education as a Service
Energy as a Service
Ejemplos de software as a Service
Ey cyber as a Service
Edi as a Service
Esb as a Service
Ejemplos de infrastructure as a Service
Ejemplos de platform as a Service
Ecommerce as a Service
Engineering as a Service
Examples of data as a Service
Experience as a Service
Examples of platform as a Service
Ethernet as a Service
Ec2 as a Service
Escrow as a Service
Elastic as a Service
Ey cybersecurity as a Service
Ehr as a Service
Etl as a Service
Equipment as a Service
Eshop as a Service
Editing as a Service
Elk as a Service
Edge as a Service
Ejemplo software as a Service
Environment as a Service
Efficiency as a Service
Ecosystem as a Service
Espace as a Service
Email encryption as a Service
Examples of software as a Service
Employee as a Service
Eugene wei status as a Service
Ehr software as a Service
Eit as a Service
Exe as a Service
Ecm as a Service
Eu mobility as a Service

FaaS

Function as a Service
Fitness as a Service
Fleet as a Service
Fit as a Service
Fax as a Service
Failure as a Service
Ffmpeg as a Service
Fabric as a Service
Food as a Service
File upload as a Service
Fw as a Service
Ff14 crafting as a Service
Fn function as a Service
Fido as a Service
Flow as a Service
Fda software as a Service
Fpga as a Service
Fhir as a Service
Factory as a Service
Ftp as a Service
Finance as a Service
Ftp server as a Service
Fte as a Service
Fm as a Service
Farming as a Service
Flask as a Service
Full as a Service
Food delivery as a Service
Flags as a Service
Fbaas functional blockchain as a Service
Feature as a Service
Ffxiv crafting as a Service
File transfer as a Service
Fees as a Service
Federation as a Service
Feedback as a Service
Fx as a Service
Feature flags as a Service

GaaS

Gpedit logon as a Service
Gpo as a Service
Gep kafka as a Service
Gitea as a Service
Gpu as a Service
Git as a Service
Gateway as a Service
Gunicorn as a Service
Gbm as a Service
Gym as a Service
Gitlab as a Service
Gcp desktop as a Service
Gps tracking as a Service
Gpo logon as a Service
Gmail as a Service
Government as a Service
Google docs is an example of software as a Service
Github as a Service
Gis as a Service
Gcp platform as a Service
Global mobility as a Service
Gcp database as a Service
Gcp blockchain as a Service
Graphics as a Service
Graphic design as a Service
Gitlab runner as a Service
Gcp infrastructure as a Service
Games as a Service
Gas as a Service
Graphql as a Service
Gpl software as a Service
Goods as a Service

HaaS

Hbase as a Service
Hfs as a Service
Healing as a Service
Hfs procurement as a Service
Hp device as a Service
Hardware as a Service
History of as a Service
Hdfs as a Service
Hw as a Service
Hilti as a Service
Hp printing as a Service
Healthcare as a Service
Highway as a Service
Hpe as a Service
Html to pdf as a Service
Hpe everything as a Service
Hpc as a Service
Hsm as a Service
Hr as a Service
Hadoop as a Service
Hana as a Service
Headset as a Service
Heating as a Service
Hvac as a Service
Hp as a Service
Health as a Service
Hci as a Service
Httpd as a Service
Hbr platform as a Service
Human as a Service
Hp printer as a Service
Hacking as a Service

IaaS

Iis as a Service
Ibm blockchain as a Service
Ibm backup as a Service
Iphone as a Service
Irc as a Service
Iaas as a Service
Immobilier as a Service
Infrastructure as a Service
Irrigation as a Service
Ico as a Service
Icinga as a Service
Ifix as a Service
Ifrs 16 software as a Service
Internet as a Service
Is a platform as a Service
Ix as a Service
Iot platform as a Service
It security as a Service
Image as a Service
Ixia testing as a Service
In platform as a Service
Iot as a Service
Iaas infrastructure as a Service
Ivanti as a Service
Ict as a Service
Ivr as a Service
Id as a Service
Iam as a Service
Identity as a Service
Iis server as a Service
Ips as a Service
Ibm platform as a Service
Ipad as a Service
Ira as a Service
Ixia lab as a Service
Iwsva as a Service
Ibm deep learning as a Service
Ios as a Service
Iis express as a Service
Ipsec as a Service
Ims as a Service
Industry as a Service
Iga as a Service
Ibm infrastructure as a Service
Ibm mainframe as a Service
Ibm software as a Service

JaaS

Job as a Service
Jde as a Service
Journalism as a Service
Jboss eap 7 as a Service
Judge as a Service
Jboss as a Service
Jira as a Service
Jmeter as a Service
Jenkins as a Service
Jdownloader as a Service
Jupyterhub as a Service
Jq as a Service
Javascript as a Service
Justice as a Service
Jboss run as a Service
Jwt as a Service
Json as a Service
Jd retail as a Service
Jfrog as a Service
Js as a Service
Java class as a Service
Jewelry as a Service
Jupyter as a Service
Jupyter notebook as a Service
Java as a Service
Jvm as a Service

Kaas

Kubectl as a Service
Kubernetes function as a Service
Kubernetes as a Service
Kpi as a Service
Kms as a Service
Knowledge base as a Service
Keyboard as a Service
Kitchen as a Service
Key management as a Service
Kpmg as a Service
Kvh connectivity as a Service
Knowledge as a Service
Kyc as a Service
Kpmg mobility as a Service
Key as a Service
Kafka as a Service
Kvm as a Service

LaaS

Leadership as a Service
Lab as a Service
Laundry as a Service
Light as a Service
Linux start as a Service
Learning as a Service
Lync as a Service
Lenovo as a Service
Lan as a Service
Library as a Service
Laser cutting as a Service
Legal as a Service
Logistics as a Service
Log on as a Service
Laptop as a Service
Linux as a Service
Luxury as a Service
Location as a Service
Life as a Service
List of as a Service
Login as a Service

MaaS

Mvno as a Service
Ml platform as a Service
Ms windows as a Service
Mdm as a Service
Mobility as a Service
Mro as a Service
Machine learning as a Service
Mdx as a Service
Ml as a Service
Ms project as a Service
Mpls as a Service
Msp and as a Service
Management as a Service
Music as a Service
Mlaas machine learning as a Service
Machine as a Service
Microsoft as a Service
Marketing as a Service
Mind as a Service
Middleware as a Service
Mfa as a Service
Mysq|i start as a Service
Mdr as a Service
Mssql as a Service
Matt broker as a Service
Mrp as a Service
Model as a Service
Mft as a Service
Maas mobility as a Service
Mysql as a Service
Matt as a Service
Microsoft desktop as a Service
Mvp as a Service
Mq as a Service
Man truck as a Service
Mongodb as a Service
Marketplace as a Service

NaaS

Ngrok as a Service
Nike as a Service
Nip as a Service
Nvidia gaming as a Service
Nps as a Service
Nlu as a Service
Ng serve as a Service
Npm start as a Service
Ntrights logon as a Service
Npm as a Service
No as a Service
Nfv as a Service
Netflix as a Service
Notification as a Service
Nms as a Service
Notebook as a Service
Ntp as a Service
Nzbget as a Service
Nfs as a Service
Nslookup as a Service
Nature as a Service
Nat as a Service
Nfaas named function as a Service
Nginx as a Service
Nexus as a Service
Network as a Service
Nzta mobility as a Service
Nmap as a Service

OaaS

Openshift as a Service
Oecd software as a Service
Omnichannel as a Service
Openvpn as a Service
Odoo as a Service
Owncloud as a Service
Omada controller as a Service
Office as a Service
Otp as a Service
Oauth2 as a Service
Oss as a Service
Oauth as a Service
Ocr as a Service
Otrs as a Service
Owin as a Service
Outlook as a Service
Operations as a Service
Organization as a Service
Ot as a Service
Orchestration as a Service
Object storage as a Service
Oracle cloud database as a Service
Outsourcing as a Service
Oms as a Service
Oem mobility as a Service
Ott as a Service
Operating system as a Service
Object as a Service
Output as a Service
Oracle cloud as a Service
On software as a Service
Oxidized as a Service
Of infrastructure as a Service

PaaS

Pipeline as a Service
Pm2 as a Service
Ptp as a Service
Police as a Service
Platform as a Service
Pharma as a Service
Pwc insights as a Service
Pump as a Service
Pbx as a Service
Python function as a Service
Philips as a Service
Pef as a Service
Phone as a Service
Pmo as a Service
Portal as a Service
Performance as a Service
Pizza as a Service
Pension as a Service
Perks as a Service
Python as a Service
Pwc dpo as a Service
Puppet as a Service
Proxy as a Service
Pci as a Service
Push as a Service
Ppt on software as a Service
Pwc software as a Service
Pos as a Service
Ppm as a Service
Product as a Service
Porsche as a Service
Ps4 as a Service
Post as a Service
Paas platform as a Service
Power as a Service
Plex as a Service
Payroll as a Service
Pure as a Service
Pigeon as a Service
Procurement as a Service
Pc as a Service
Python program as a Service
Pdf as a Service
Pgbouncer as a Service
Pms as a Service
Payment as a Service
Pivotal function as a Service
Psexec as a Service
Parking as a Service
Ping as a Service
Python script as a Service
Push notifications as a Service
Pwc mobility as a Service

QaaS

Quality as a Service
Qbittorrent run as a Service
Qradar as a Service
Qr code as a Service
Qover insurance as a Service
Qic mobility as a Service
Qitc as a Service
Qa automation as a Service
Qa as a Service
Qa testing as a Service
Quality management as a Service
Qvartz mobility as a Service
Qsync as a Service
Qlik web connectors as a Service
Qlik as a Service
Qbittorrent as a Service
Qkd as a Service
Quant as a Service

RaaS

Rds as a Service
Room as a Service
Retail as a Service
Rolls royce as a Service
Rsyslog as a Service
Rabbit as a Service
Rsync as a Service
Ransomware as a Service
Rtb bidder as a Service
Rng as a Service
Run exe as a Service
Rh as a Service
Run plex as a Service
Rpa software as a Service
Rclone as a Service
Run onedrive as a Service
Rhapsody as a Service
Rss as a Service
Radio as a Service
Run dropbox as a Service
Radius as a Service
Run unifi as a Service
Rdp as a Service
Run unifi controller as a Service
Rtorrent as a Service
Ras as a Service
Rpa as a Service
Risk as a Service
Rap as a Service
Roaming as a Service
Rfp for soc as a Service
Rfp as a Service
Rfp software as a Service
Rslinx running as a Service
Rbac as a Service
Rfid as a Service
Right to log on as a Service
Robotics as a Service
Run powershell script as a Service
Rabbitmg as a Service
Rsa as a Service
Res as a Service
Rem as a Service

SaaS

Sdr as a Service
Software as a Service
Snail mail as a Service
Synology as a Service
Snow as a Service
Swarm as a Service
Sleep as a Service
Sccm as a Service
Science as a Service
Slack as a Service
Strategy as a Service
Script powershell as a Service
Spark as a Service
Swift as a Service
Sql database as a Service
Symfony factory as a Service
Storage as a Service
Spring boot as a Service
Sftp as a Service
Skills as a Service
Syslog as a Service
Sql server as a Service
Svn as a Service
Switch as a Service
Sdn as a Service
Support as a Service
Saas as a Service
Service desk as a Service
Ship as a Service
Swagger as a Service
Symfony controller as a Service
Supply chain as a Service
Streaming as a Service
Scheduling software as a Service
Symfony repository as a Service
Smtp as a Service
Smtp relay as a Service
Sd-wan as a Service
Shop as a Service
Scanning as a Service
Subscription as a Service
Sap as a Service
Shell as a Service
Site as a Service
Sam as a Service
Sms gateway as a Service
Spring as a Service
Space as a Service
Skype as a Service
Slice as a Service
Smart home as a Service
Sip as a Service
Smartphone as a Service
School as a Service
Sdk as a Service
Shadow gaming as a Service
Signalr as a Service
Sbe as a Service
Surface as a Service
Sv as a Service
Sms as a Service
Status as a Service
Secret as a Service
Snowflake as a Service
Sales as a Service
Saas software as a Service
Snackbar as a Service
Sprint as a Service
Security as a Service
Siem as a Service
System as a Service

TaaS

Task scheduler as a Service
Tape as a Service
Testing as a Service
Team as a Service
Types of cloud as a Service
Train as a Service
Tftpd32 is running as a Service
Tftpd as a Service
Tyco as a Service
Tts as a Service
Teamviewer as a Service
Thm as a Service
Training as a Service
Tcpdump as a Service
Tunnel as a Service
Tivo as a Service
Tech as a Service
Transportation as a Service
Tftpd64 as a Service
Tms as a Service
Truck as a Service
Translation as a Service
Tyre as a Service
Two factor authentication as a Service
Tftp as a Service
Tftp server as a Service
Time as a Service
Table as a Service
Tightvnc as a Service
Tfs as a Service
Types of as a Service
Turn as a Service
Telephony as a Service
Tool as a Service
Tftpd32 as a Service
Tax as a Service
Tv as a Service

UaaS

Uwp as a Service
Uav as a Service
Uma as a Service
Umbraco as a Service
User profile as a Service
Unifi controller run as a Service
Uipath as a Service
Uber as a Service
Ux research as a Service
Upload as a Service
Uptime as a Service
Ux as a Service
Uitp mobility as a Service
Unifi as a Service
Ui components as a Service
Ups as a Service
Ultravnc as a Service
Uwsgj as a Service
Uat as a Service
Unified communications as a Service
Url shortener as a Service
User as a Service
Update as a Service
Ultrasound as a Service
Ubuntu run as a Service
Unifi controller as a Service
Ubuntu start mongodb as a Service
Uipath orchestrator as a Service
Uipath rpa as a Service
Ui as a Service
Uipath robot as a Service
Utility as a Service
Ubuntu as a Service
Uber mobility as a Service

VaaS

Vdi desktop as a Service
Vault encryption as a Service
Vulnerability management as a Service
Volvo as a Service
Vsts agent as a Service
Voice as a Service
Vision as a Service
Vehicle as a Service
Vault as a Service
Vnc as a Service
Ve as a Service
Vmware infrastructure as a Service
Vdi vs desktop as a Service
Vpp as a Service
Voicemail as a Service
Vb.net run program as a Service
Vod as a Service
Vdi as a Service
Vastgoed as a Service
Vnf as a Service
Vb6 as a Service
Vtiger as a Service
Video streaming as a Service
Vmware as a Service
Vbox as a Service
Vmware desktop as a Service
Value as a Service
Vpn as a Service
Vic as a Service
Vad ar en as a Service
Vr as a Service
Volvo car as a Service
Video editing as a Service
Virtualization as a Service
Vcenter as a Service
Voip as a Service
Vpc as a Service
Vmware workstation as a Service
Volte as a Service
Vbs as a Service
Vulnerability assessment as a Service
Vbscript as a Service
Vncserver as a Service
Vulnerability scanning as a Service
Video as a Service
Vmware kubernetes as a Service
Vb.net as a Service
Value chain as a Service

WaaS

Website as a Service
Word as a Service
Web security as a Service
Wfm as a Service
Wpf as a Service
Web scraping as a Service
Workstation as a Service
Wgl energy as a Service
Web as a Service
Wp curve Service as a Service
Wef as a Service
Water as a Service
Wsl as a Service
What is mobility as a Service
What is platform as a Service
What is infrastructure as a Service
Warehouse as a Service
Wsus as a Service
Wifi as a Service
Washing machine as a Service
Wmi as a Service
Workforce as a Service
Waiter as a Service
Windows update as a Service
What is marketing as a Service
What is software as a Service
What is security as a Service
Wrapper as a Service
Windows as a Service
Windows python as a Service
Warmte as a Service
Workplace as a Service
Watch as a Service
Windows install as a Service

XaaS

Xaas everything as a Service
Xampp install apache as a Service
Xenmobile as a Service
Xaas anything as a Service
Xwiki as a Service
Xampp as a Service
Xbox games as a Service
Xendesktop as a Service
Xbox as a Service
Xinet as a Service
Xenapp as a Service
Xaas as a Service
Xmpp as a Service
Xcode as a Service
Xtaas telemarketing as a Service

YaaS

Youtube as a Service

ZaaS

Zangyou as a Service
Zscaler as a Service
Zookeeper as a Service
Zabbix as a Service
Zf mobility as a Service
Zscaler security as a Service
Zdnet windows as a Service
Zabbix agent as a Service
Zeppelin as a Service
Zscaler firewall as a Service
Zero carbon as a Service
Zap as a Service

終わりに

とてつもない量のサービスですね。今まで普通にあったサービスも、ゴロがいいからみたいな理由で、as a Serviceと名づけられてるのかなと思いました。
これからは何かをしたいと思う時、自分で準備するよりもサービスを利用した方がいいということでしょうか。
以下の2年ほど前の記事にもまとまっていたので紹介しておきます。
https://boxil.jp/mag/a3600/

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

PolyglotをRaspberry Piで動かして英文の形態素解析を行う

Advent calendarのネタづくりを行うときに英文の形態素解析を行う必要が生まれたのですが少し引っかかったので残しておきます。

Polygotとは何か

日本語の領域だとMeCabが形態素解析ツールとしてよく取り上げられていますが、英文の形態素解析となるとそんなに事例が多くありません。
そんな英文の形態素解析を始めとして言語特定、言語検出などの自然言語解析の機能を多く持ったライブラリが、Polygotです。

言語サポート範囲

公式ドキュメントに記載されてる通りになりますが機能ごとによって対応言語に差があります。

機能名 対応言語数 説明
トークン化 165言語 文字列を自然言語処理を行う際に扱う文章の最小単位に分割します
言語検出 196言語 解析対象の文字列の言語を特定します
固有表現抽出 40言語 解析対象の文字列から固有表現を抽出します Polygotでは、場所組織の3つのタイプを抽出することができます
品詞タグづけ 16言語 解析対象の文字列の各トークンに対して、品詞タグを付与します
感情分析 136言語 ネガティブニュートラルポジティブの3つの種別を取得できる
分散表現 137言語 単語をd次元のベクトル空間にマッピングします
形態素解析 135言語 解析対象の文字列から意味を持つ最小単位に分割します
翻字 69言語 入力された文字列を指定した言語の文字列に変換する

上記表から見てもわかるように多くの言語をサポートしています。

Install

実際にPolygotを動くようにセットアップしていきましょう。

Polygotをインストールする

$ sudo pip3 install -U polyglot

polyglot自体は、上記コマンドを実行するだけでインストールすることができます。
しかし、実際にpolyglotで言語解析を行うには解析対象の言語の辞書を取得する必要があります。
辞書取得時にICUがインストール済みでないとエラーを吐きます。なので、ダウンロード前に下記コマンドを実行して必要なライブラリを取得します。

$ sudo apt-get -y install libicu-dev
$ sudo pip3 install -U pyicu
$ sudo pip3 install -U morfessor

その他にpycld2がモデルのダウンロードに必要となります。
通常のLinux環境であれば$ sudo pip install pycld2を叩くだけでインストールすることが可能です。ですが、Raspberry Pi上で上記コマンドを実行すると下記のようなエラーが表示されていまいます。

arm-linux-gnueabihf-gcc: error: unrecognized command line option ‘-m64’
  error: command 'arm-linux-gnueabihf-gcc' failed with exit status 1
  ----------------------------------------
  ERROR: Failed building wheel for pycld2

上記エラーはARMアーキテクチャ向けのコンパイラーに-m64オプションが用意されていないことでコンパイルに失敗しているため発生しています。
このままでは、pycld2をインストールすることができないため、Raspberry Pi上でPolyglotを動かすことが出来ません。さぁ困った....

pycld2をRaspberry Piにインストールする

そのままでは、インストールできないためpycld2のsetup.pyに指定されている-m64コンパイルオプションを取り除いた上でsetup.pyを実行する必要があります。
下記のリポジトリからgit cloneした上で、setup.pyを弄ります。
aboSamoor/pycld2 - Github

$ git clone https://github.com/aboSamoor/pycld2.git
$ cd pycld2/

git cloneしたpycld2のディレクトリに移動し、直下に配置されているsetup.pyのLine 78に記述されているコンパイルオプションの配列から-m64を削除してから保存します。

変更前
    language="c++",
    # TODO: -m64 may break 32 bit builds
    extra_compile_args=["-w", "-O2", "-m64", "-fPIC"],
変更後
    language="c++",
    # TODO: -m64 may break 32 bit builds
    extra_compile_args=["-w", "-O2", "-fPIC"],

変更後、下記コマンドを実行します。

$ sudo pip3 install hogehoge/pycld2/
Successfully built pycld2
Installing collected packages: pycld2
Successfully installed pycld2-0.42

実行後、Successfullyが表示されればインストール成功です。

モデルのダウンロードを行う

下記コマンドを実行するモデルのダウンロードができます。
今回は英文の形態素解析を行うので、英語のモデルをダウンロードします。

$ polyglot download morph2.en
[polyglot_data] Downloading package morph2.en to
[polyglot_data]     /home/pi/polyglot_data...

実際に形態素解析を行う

あとは下記のようなサンプルコードを実行するだけです。

morph.py
from polyglot.text import Text

sample_text = "One Hamburger and a Medium Coffee please."
tokens = Text(sample_text)
print(tokens.morphemes)

実際に上記スクリプトを実行すると下記のような形で結果を取得することが可能です。

$ python3 morph.py 
['One', ' ', 'Ham', 'burg', 'er and a Medium Coffee p', 'lease', '.']

おわりに

今回はとあるプログラムを作るためにPolyglotを初めて活用しました。言語判定などできるので、TwitterAPIと絡めて日本語であればMeCab側で処理して、それ以外をPolyglotに任せるなども出来るかと思います。中々、英文の自然言語処理を業務で使うことは無いと思いますが1つの引き出しとして備忘録的に残しておきます。

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

pyenv が homebrew でインストールした tcl-tk と動かない。

遭遇した問題

Macでpyenvを使ってインストールしたpythonから、tkinter を使おうとしたところ、次のエラーがでた。

Traceback (most recent call last):
  File "./annotate.py", line 3, in <module>
    import tkinter
  File "/Users/???/.pyenv/versions/3.7.4/lib/python3.7/tkinter/__init__.py", line 36, in <module>
    import _tkinter # If this fails your Python may not be configured for Tk
ModuleNotFoundError: No module named '_tkinter'

環境は、

  • MacOS Catalina 10.15.1
  • pyenv 1.2.15

ググって見つかった(そして解決しなかった)方法

pyenv で python を再インストールする

pyenv でpythonをインストールしている場合、一旦 uninstall して install し直すと解決するらしい。

ググって見つけたのは以下のページ:

具体的には、↓な感じ。

$ pyenv versions
 system
* 3.7.5                 # 現在使われている python が 3.7.5 だったので
$ pyenv uninstall 3.7.5 # python 3.7.5 をアンインストールして
$ brew install tcl-tk   # homebrew で tcl-tk をインストールして
$ export LDFLAGS="-L/usr/local/opt/tcl-tk/lib"      # tcl-tk の開発に必要な
$ export CPPFLAGS="-I/usr/local/opt/tcl-tk/include" # 徹底を行ってから
$ pyenv install 3.7.5   # 改めて python 3.7.5 をインストールする

しかし、再度 tkinter を実行してみると・・・

$ python -m tkinter
DEPRECATION WARNING: The system version of Tk is deprecated and may be removed 
in a future release. Please don't rely on it. Set TK_SILENCE_DEPRECATION=1 to suppress this warning.
Traceback (most recent call last):
  File "/Users/???/.pyenv/versions/3.7.4/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/Users/???/.pyenv/versions/3.7.4/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File 
"/Users/???/.pyenv/versions/3.7.4/lib/python3.7/tkinter/__main__.py", line 7, in <module>
main()
  File "/Users/???/.pyenv/versions/3.7.4/lib/python3.7/tkinter/__init__.py", line 3988, in _test
    root = Tk()
  File "/Users/???/.pyenv/versions/3.7.4/lib/python3.7/tkinter/__init__.py", line 2025, in __init__
    self._loadtk()
  File "/Users/???/.pyenv/versions/3.7.4/lib/python3.7/tkinter/__init__.py", line 2040, in _loadtk
    % (_tkinter.TK_VERSION, tk_version))
RuntimeError: tk.h version (8.6) doesn't match libtk.a version (8.5)

と別のエラーが出てしまう。
この場合、header は version 8.6 を使ってコンパイルされるが、実行時に参照されるライブラリが version 8.5 担っているということらしい。

さらにググるも、日本語の解は見つからず。

解決策

英語でググったところ、次のページが見つかった。

これですね。

つまり、/usr/local/Cellar/pyenv/VERSION/plugins/python-build/bin/python-build を編集して、次のパッチを当てる(と言っても、銭湯が "!" となっている1行書き換える)。

diff -c python-build.orig python-build
*** python-build.orig   2019-12-10 17:47:04.000000000 +0900
--- python-build.new    2019-12-11 11:53:17.000000000 +0900
***************
*** 772,778 ****
        export CC=clang
      fi
      ${!PACKAGE_CONFIGURE:-./configure} --prefix="${!PACKAGE_PREFIX_PATH:-$PREFIX_PATH}" \
!       $CONFIGURE_OPTS ${!PACKAGE_CONFIGURE_OPTS} "${!PACKAGE_CONFIGURE_OPTS_ARRAY}" || return 1
    ) >&4 2>&1

    { "$MAKE" $MAKE_OPTS ${!PACKAGE_MAKE_OPTS} "${!PACKAGE_MAKE_OPTS_ARRAY}"
--- 772,778 ----
        export CC=clang
      fi
      ${!PACKAGE_CONFIGURE:-./configure} --prefix="${!PACKAGE_PREFIX_PATH:-$PREFIX_PATH}" \
!       $CONFIGURE_OPTS --with-tcltk-includes='-I/usr/local/opt/tcl-tk/include' --with-tcltk-libs='-L/usr/local/opt/tcl-tk/lib -ltcl8.6 -ltk8.6            ' ${!PACKAGE_CONFIGURE_OPTS} "${!PACKAGE_CONFIGURE_OPTS_ARRAY}" || return 1
    ) >&4 2>&1

    { "$MAKE" $MAKE_OPTS ${!PACKAGE_MAKE_OPTS} "${!PACKAGE_MAKE_OPTS_ARRAY}"

それで、pyenv で python を再インストールすれば良い。

$ pyenv uninstall 3.7.5
$ pyenv install 3.7.5
$ python -m tkinter

うまく行った。

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

Anaconda on Windows Terminal

Anaconda on Windows Terminal

前回のROS on Windows Terminalに続いて、今回は「Anaconda on Windows Terminal」です。

setting.json

Windows Terminalを管理者として実行し、+からsettingを選びます。
そうすると、setting.jsonが開くので、

        {
            "acrylicOpacity": 1.0,
            "closeOnExit": true,
            "colorScheme": "Campbell",
            "commandline": "cmd.exe /k C:\\Users\\username\\Anaconda3\\Scripts\\activate.bat",
            "cursorColor": "#FFFFFF",
            "cursorShape": "bar",
            "fontFace": "consolas",
            "fontSize": 14,
            "guid": "{xxx}",
            "historySize": 9001,
            "name": "Anaconda",
            "padding": "0, 0, 0, 0",
            "snapOnInput": true,
            "startingDirectory": "%HOME%",
            "tabTitle": "Anaconda",
            "useAcrylic": true,
            "icon": "ms-appx:///ProfileIcons/{0caa0dad-35be-5f56-a8ff-afceeeaa6101}.png"
        }

あとは少々変更

  • ユーザ名(username)を自分の物に
  • Anacondaのディレクトリの場所が正しいのか確認
  • "startingDirectory"の変更

より詳しい説明

画像が多くわかりやすいので、英語ですがもしわからなければこちらを参照してください。

結果

たくさんできた
Windows Terminal

help me

複数の仮想環境のターミナルを入れることはできないのか...?

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

Slackに飛んでくるブックマークをDoc2VecとPCAで可視化してみた

アドベントカレンダーの11日目の記事です

これは

みんな(4人)のブックマークの分散表現を獲得して可視化したもの

ブックマークするとIFTTTが拾ってslackに吐くようにしているのでそこから処理する

前提条件

参考文献/利用したもの

  1. 【転職会議】クチコミをword2vecで自然言語処理して会社を分類してみる
  2. 【word2vec】会社のクチコミを自然言語処理した結果を可視化してみる
  3. pythonによる日本語前処理備忘録
  4. slackのチャットログをお手軽バックアップ
  5. gensim
  6. slack-dump
  7. slack api

できるもの

やる前の予想

  • Rくん
    • ガジェットやセキュリティ系が多い
    • ブクマ数79
  • Yさん
    • 4人の中で一番範囲が広い
    • 実はこのユーザだけみんなにシェアする目的で取捨選択したものを投稿している
    • ブクマ数864
  • Mくん
    • Webと機械学習など
    • ブクマ数240
  • S(自分)
    • Webと機械学習とガジェットに加えて「今年はサンマが不漁」みたいなのまで投げてしまう
    • ブクマ数896

結果

なんとなくそうなった気がする

スクリーンショット 2019-12-11 19.16.44.png

準備

はてなブックマークをIFTTTにSlackへ投稿させる仕組み

手順

下図の4コマ目と5コマ目の間にRSS Feedを受け取るためのURLを入力する
今回ははてなブックマークなので
http://b.hatena.ne.jp/<username>/rss
となる

こんな感じ

理由

はてな内でユーザをお気に入りすればいい?

それでも悪くはない(むしろ両方してもいい)が、コミュニティ内だとこんな感じで気軽にコメントしあえる

IMG_52CF406E5EED-1.jpeg

Slackコマンドの/feedをつかえばいい?

投稿文をカスタマイズできるので今回のように遊びに使える、またSlackコマンドを使うとめちゃくちゃスペースを取るので困る

Slackから投稿メッセージを取得

簡単にできそうなのはこの2種類

  1. SlackのAPI
  2. Go製のツール(今回はこっち)

どちらにせよトークンが必要なのでここから取得

$ wget https://github.com/PyYoshi/slack-dump/releases/download/v1.1.3/slack-dump-v1.1.3-linux-386.tar.gz
$ tar -zxvf slack-dump-v1.1.3-linux-386.tar.gz
$ linux-386/slack-dump -t=<token> <channel>

DMとかもいっしょに取ってきて邪魔なので 別のところに移す

python
import zipfile, os

os.mkdir('dumps')
with zipfile.ZipFile('./zipfize_name') as z:
    for n in z.namelist():
        if 'channel_name' in n:
            z.extract(n, './dumps')

ファイルを開いて中身を取得する、日付ごとになっているので全部を1つにする

python
import json, glob

posts = []
files = glob.glob('./dumps/channel/{}/*.json'.format(dirname))
for file in files:
    with open(file) as f:
        posts += json.loads(f.read())

Messageを取り出して記事タイトルとユーザ名を紐づける(この辺はIFTTTでの設定による)

python
user_post_dic = {
    'Y': [],
    'S': [],
    'M': [],
    'R': [],
}

for p in posts:
    if "username" not in p or p["username"] != "IFTTT":
        continue
    for a in p["attachments"]:
        # 雑回避
        try:
            user_post_dic[a["text"]].append(a["title"])
        except:
            pass

users = user_post_dic.keys()
print([[u, len(user_post_dic[u])] for u in users])
出力
[['Y', 864], ['S', 896], ['M', 240], ['R', 79]]

本編

前処理

クレンジングとわかち書き

投稿されるメッセージはこんな感じになっていてサイトのタイトルやURLは不要なので削除する

ブラウザのテキストエリアでNeovimを使う  <http://Developers.IO|Developers.IO>

フロントエンドエンジニアのためのセキュリティ対策 / #frontkansai 2019 - Speaker Deck

matplotlibで日本語

モダンJavaScript再入門 / Re-introduction to Modern JavaScript - Speaker Deck

reを使う時のお作法がよくわからなかったのでゴリ押し
加えて、MeCabでの分かち書きもおこなう、環境にはsudachipyなども入っているが、手に馴染んでいるものをつかう、速いし

python
import MeCab, re
m = MeCab.Tagger("-Owakati")

_tag = re.compile(r'<.*?>')
_url = re.compile(r'(http|https)://([-\w]+\.)+[-\w]+(/[-\w./?%&=]*)?')
_title = re.compile(r'( - ).*$')
_par = re.compile(r'\(.*?\)')
_sla = re.compile(r'/.*$')
_qt = re.compile(r'"')
_sep = re.compile(r'\|.*$')
_twi = re.compile(r'(.*)on Twitter: ')
_lab = re.compile(r'(.*) ⇒ \(')
_last_par = re.compile(r'\)$')

def clean_text(text):
    text = text.translate(str.maketrans({chr(0xFF01 + i): chr(0x21 + i) for i in range(94)}))
    text = re.sub(_lab, '', text)
    text = re.sub(_tag, '', text)
    text = re.sub(_url, '', text)
    text = re.sub(_title, '', text)
    text = re.sub(_sla,  '', text)
    text = re.sub(_qt,  '', text)
    text = re.sub(_sep, '', text)
    text = re.sub(_twi, '', text)
    text = re.sub(_par, '', text)
    text = re.sub(_last_par, '', text)
    return text

p_all = []
m_all = []
for u in users:
    user_post_dic[u] = list(map(clean_text, p_dic[u]))
    m_all += [m.parse(p).split('\n')[0] for p in p_dic[u]]
    p_all += [u + '**' + p for p in user_post_dic[u]]

p_allで各要素の頭にユーザ名を付けたのは前処理によってテキストが消滅してしまい、listのindexがずれてしますため、苦し紛れで紐づけている
(ちなみにURLを記事タイトルとしてブクマしている場合など)

一応はきれいになった

ブラウザのテキストエリアでNeovimを使う 

フロントエンドエンジニアのためのセキュリティ対策

matplotlibで日本語

モダンJavaScript再入門 

Doc2Vec

m_allが分散表現を獲得する時の材料となる文章本体
p_allは呼び方にすぎない

パラメータは熱心には検討していない

python
from gensim import models

# 参考記事: http://qiita.com/okappy/items/32a7ba7eddf8203c9fa1
class LabeledListSentence(object):
    def __init__(self, words_list, labels):
        self.words_list = words_list
        self.labels = labels

    def __iter__(self):
        for i, words in enumerate(self.words_list):
            yield models.doc2vec.TaggedDocument(words, ['%s' % self.labels[i]])

sentences = LabeledListSentence(m_all, p_all)
model = models.Doc2Vec(
    alpha=0.025,
    min_count=5,
    vector_size=100,
    epoch=20,
    workers=4
)
# 持っている文から語彙を構築
model.build_vocab(sentences)
model.train(
    sentences,
    total_examples=len(m_all),
    epochs=model.epochs
)

# 順番が変わってしまうことがあるので再呼び出し
tags = model.docvecs.offset2doctag

PCAと描画

PCAのライブラリを利用するのは初めてで、あんなに手順を踏んで学んだのに2行で使えてすごい

python
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import japanize_matplotlib

vecs = [model.docvecs[p] for p in tags]
draw_scatter_plot(vecs, ls)

# 紐付けを解く
tag_users = [p.split('**')[0] for p in tags]
tag_docs = [p.split('**')[1] for p in tags]

# 4色で同じ程度の色感を見つけるのは難しかった
cols = ["#0072c2", "#Fc6993", "#ffaa1c", "#8bd276" ]

# 無理に1行で書いた
clusters = [cols[0] if u == tag_users[0] else cols[1] if u == tag_users[1] else cols[2] if u == tag_users[2] else cols[3] for u in lab_users]

# 平面なので2次元
pca = PCA(n_components=2)
coords = pca.fit_transform(vecs)

fig, ax = plt.subplots(figsize=(16, 12))
x = [v[0] for v in coords]
y = [v[1] for v in coords]

# 凡例をつけるためにこのループをする
for i, u in enumerate(set(tag_users)):
    x_of_u = [v for i, v in enumerate(x) if tag_users[i] == u]
    y_of_u = [v for i, v in enumerate(y) if tag_users[i] == u]
    ax.scatter(
        x_of_u,
        y_of_u,
        label=u,
        c=cols[i],
        s=30,
        alpha=1,
        linewidth=0.2,
        edgecolors='#777777'
    )

plt.legend(
    loc='upper right',
    fontsize=20,
    prop={'size':18,}
)
plt.show()

できたもの(再掲)

やる前の予想

  • Rくん
    • ガジェットやセキュリティ系が多い
    • ブクマ数79
  • Yさん
    • 4人の中で一番範囲が広い
    • 実はこのユーザだけみんなにシェアする目的で取捨選択したものを投稿している
    • ブクマ数864
  • Mくん
    • Webと機械学習など
    • ブクマ数240
  • S(自分)
    • Webと機械学習とガジェットに加えて「今年はサンマが不漁」みたいなのまで投げてしまう
    • ブクマ数896

結果

なんとなくそうなった気がする

スクリーンショット 2019-12-11 19.16.44.png

おわり

もう少しデータがあつまればユーザの推論とか回してレコメンドとかしたいですね
遅れてすみませんでした(12/11/21:00)

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

Pythonで定義元や定義先を参照できるjedi-vimのショートカットコマンド

jedi-vimは、VimにおけるPythonの入力補完ツール。
入力補完だけでなく、定義先や定義元へのジャンプもできたりする。
JavaのEclipseなどのIDEツールで使える機能みたいなやつ。

コマンド忘れがちなので、備忘録として書いておきます。

項目 キー 説明
g:jedi#completions_command <Ctrl-Space> 補完開始
g:jedi#goto_command <leader>d Definition(またはAssignment)に移動
g:jedi#goto_assignments_command <leader>g Assignmentに移動
g:jedi#documentation_command <K> pydoc表示
g:jedi#rename_command <leader>r 変数リネーム
g:jedi#usages_command <leader>n 使用箇所表示
:Pyimport :Pyimport モジュールのオープン
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NMF(非負値行列因子分解)の初期値問題

この記事は古川研究室 Advent_calendar 11日目の記事です。
本記事は古川研究室の学生が学習の一環として書いたものです。内容が曖昧であったり表現が多少異なったりする場合があります。

はじめに

前回の記事はNMFとは何なのか?などNMFを初めて勉強する方への記事でした。本記事ではsklearnのNMFライブラリーを実装し初期値による誤差の違いを見ていきます。

NMFの初期値問題

NMFでは学習を始める前に$W,H$の初期値を設定する必要があります。下の図は学習1回目の初期値をランダムにした場合です。$W,H$にランダムな値を当てはめて学習1回目の推定値$\hat{Y}$を計算します。その後元データ$Y$との誤差を計算し、$||Y-\hat{Y}||$が小さくなるように$W,H$を更新していきます。このランダム初期値の場合、学習後の推定値$\hat{Y}$がランダム値によって異なります。つまりランダム値により毎回違う推定値$\hat{Y}$が出てきてしまうという問題があります。NMFの初期値問題を解決する方法は多々あるのですが、一定の推定値$\hat{Y}$を得るためにsklearnのNMFライブラリーではランダム初期化以外にnndsvd,nndsvda,nndsvdarを選択でき、これらの手法で初期化すると一定の推定値を求めることが出来ます。nndsvdについては参考論文[1]こちらをご覧ください。

image.png

sklearn.decomposition.NMF

sklearnのNMFでは初期化方法に5種類選択できます。
今回はカスタム初期値以外の4種類(nndsvd,nndsvda,nndsvdar,random)の手法を比較します。
誤差はフロベニウスを用いています。比較結果が以下になります。

image.png

randomは10回の平均誤差を表示しています。100回学習するとランダム初期値よりも他の手法の誤差が小さくなっています。またnndsvd,nndsvdarは学習初期段階での誤差がランダムより小さいことが分かります。よってこのデータに関しては初期値にnndsvdを用いた方がよさそうです。

Python code

from sklearn.decomposition import NMF
import matplotlib.pyplot as plt
import numpy as np

np.random.seed(1)
X = np.random.rand(100, 10)
x_plot=np.arange(1,11,1)
time=100
x_plot_t = np.arange(1, time+1, 1)

loss_t = np.ones(time)
loss_t1 = np.empty((time,100))
loss_t2 = np.empty(time)
loss_t3 = np.empty(time)


for j in range(time):



    model_t = NMF(n_components= 10, init='nndsvd', random_state=1, max_iter=j+1, beta_loss=2,solver='cd')# ,l1_ratio=1,alpha=0.7)
    Wt = model_t.fit_transform(X)
    Ht = model_t.components_
    loss_t[j] = model_t.reconstruction_err_


    model_t2 = NMF(n_components=10, init='nndsvda', random_state=1, max_iter=j + 1, beta_loss=2,solver='cd' )#,l1_ratio=1,alpha=0.7)
    Wt2 = model_t2.fit_transform(X)
    Ht2 = model_t2.components_
    loss_t2[j] = model_t2.reconstruction_err_

    model_t3 = NMF(n_components=10, init='nndsvdar', random_state=1, max_iter=j + 1, beta_loss=2,solver='cd')# ,l1_ratio=1,alpha=0.7)
    Wt3 = model_t3.fit_transform(X)
    Ht3 = model_t3.components_
    loss_t3[j] = model_t3.reconstruction_err_

for j in range(100):

    for r in range(10):
        model_t1 = NMF(n_components=10, init='random', random_state=r, max_iter=j+1, beta_loss=2,solver='cd')#, l1_ratio=1, alpha=0.7)
        Wt1 = model_t1.fit_transform(X)
        Ht1 = model_t1.components_
        loss_t1[j,r] = model_t1.reconstruction_err_




loss_t1 = np.sum(loss_t1, axis=1) * 0.1
plt.plot(x_plot_t,loss_t,label="nndsvd",color='b')
plt.plot(x_plot_t, loss_t1,color='red',label="random")
plt.plot(x_plot_t, loss_t2,label="nndsvda",color='orange')
plt.plot(x_plot_t, loss_t3,label="nndsvdar",color='g')


plt.xlabel("epoch")
plt.ylabel("error")
plt.legend()
plt.show()

参考文献

[1] http://scgroup.hpclab.ceid.upatras.gr/faculty/stratis/Papers/HPCLAB020107.pdf
[2] https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.NMF.html

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

pandasの時系列プロットの時間軸を、matplotlibでフォーマットし直す

df.plot()後にset_major_formatter()すると、xtickの年や月の表記が壊れる問題に悩まされていたが、解決したのでメモ。

前提

pandas:0.24.2
matplotlib:3.1.0

データの準備

import pandas as pd
import numpy as np

N = 100
x = np.random.rand(N)
y = x**2

df = pd.DataFrame(
    index=pd.date_range('2020-01-01', periods=N, freq='D'),
    data=dict(x=x, y=y)
)
df.head()

image.png

問題ないケース

# pandasのプロット機能を使用
df.plot()

image.png
DataFrameのindexがDatetimeIndexの場合、自動的にtickをフォーマットしてくれる。
このフォーマットでOKなら、それでよし。

問題になるケース

import matplotlib.dates as mdates

# pandasのプロット機能を使用
ax = df.plot()

# フォーマットし直す
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y/%m/%d'))

image.png

%Yに対する表示がおかしい。0051年て何・・・。
下記サイトによれば、pandasとmatplotlibのdatetimeユーティリティは互換性がないことが原因らしい。
https://code-examples.net/ja/q/2a2a615

解決策

import matplotlib.dates as mdates

# x_compatオプションを渡す。これでtickの自動調整が抑制される。
ax = df.plot(x_compat=True)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y/%m/%d'))

image.png

%Yが正しく2020年になった!
※この例だと「pandasのフォーマットのほうがよくない?」となってしまいますが、そこはご容赦を・・

x_compatはx_compatibilityの略?
公式ドキュメントによれば、x_compatはtickの自動調整を抑制するパラメータとのこと。
https://pandas.pydata.org/pandas-docs/stable/user_guide/visualization.html#suppressing-tick-resolution-adjustment

もっとスマートなやり方がありましたら、ご教授いただければ幸いです。

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

Webサービス(CtoC)集客・マネタイズ成功させたければこれを実装しろ

はじめまして。加藤です。
本業はインフラエンジニアですが、技術書典で技術系同人誌を頒布したりWebサービスを個人開発したりしています。

さて、突然ですが、あなたはWebサービスを個人開発して公開していますか。

そして「せっかく開発したのに誰も利用してくれない」「マネタイズ(収益化)したいが上手くいかない」といった悩みを抱えていないでしょうか。

PythonやRuby、PHPなどの言語やそれらのフレームワークを使ってWebサービスを個人開発し、Firebaseなどで公開する人たちは年々増えており、彼らと話す機会も多いので、そういった声をよく聞きます。

私はそれを聞くたびに心の中で「そりゃ当たり前だ!あんなクソサービス誰も使わないわ。」と叫んでいます。

脳内妄想をこじらせた技術オタクがゴミを生む

私を含めて、Webサービスを個人開発している人たちの大多数がしている会話は「○○の言語と△△のフレームワークを使って開発しました~~」とか「Firebaseを使って~~」「フロントエンドは□□で~~」みたいにどんな技術を用いて開発したかを語るものばかりです。

「お前ら、マネタイズがしたいんだよね?マーケティングの話とかしないの?」
「素晴らしいサービス(自称)だから黙っていれば利用者が増えてスケールすると思ってない?」
「口ではマネタイズに困っているとか言いながら、そのための行動も勉強もしてないじゃん。」

と感じるようになって、私は自然と彼らと距離を置くようになりました。

自戒を込めて言うと、世の中には業務改善に役立った素晴らしいサービス(この伝票開発アプリとか)やマシュマロ(質問箱)のように多く人が利用したサービスもありますが、大多数は技術者が「面白いサービスを思いついた。こりゃ流行るぞ!」とか「この人たちはきっとこれに悩んでいるから、こういったサービスなら課題解決できるに違いない」と自分の脳内妄想を実現させた結果生みだしたゴミなので過疎って終了するわけです。

「金稼ぎ」や「欲求充足」ができる仕組みを実装しろ

では、どうしたら利用者が増えるサービス開発ができて、その結果マネタイズが成功するのか。

これを研究してきた私がたどり着いたのが、利用者が「金稼ぎ」と「欲求充足」ができる機能を実装したサービスを作れでした。

その証拠にYoutuberが雨後の筍のように増えていますし、ニコ生主等が続々とVTuberに転生してYouTubeやMirrativ等で配信していますし、小説家になろうでは書籍化を目指して多くの作家が小説公開していて、絵師や同人漫画家がTwitterやPixivでイラスト・マンガを投稿して、いまだ多くのブロガーが無料ブログやWordPressで記事を投稿しています。

みんな本音を言えば「(広告収入・投げ銭・書籍化・マンガ化等で)大金を稼ぎたい」「承認欲求を満たしたい」から、それらのサービスを使ってせっせと活動しているんです。
※好きでやっている勢もいるでしょうが、なぜ1円にもならないのにPVや再生数が伸びなくてもクソリプやアンチコメが来ても作品・動画の投稿が続けられるかといったら、ここに集約されます。

そして、それらの実装のために参考になるWebサービスが無料小説投稿サイトです。

詳細は後述しますが、収益還元の仕組みや作家・ユーザー相互の交流機能により「金稼ぎ」と「欲求充足」がどちらもできるからですね。

あなたが手っ取り早く集客やマネタイズに成功するWebサービスが作りたいのなら「大金がほしい」「注目されたい、ちやほやされたい、モテたい」といった人間が普遍的に持つ欲を(法律に抵触しない範囲で)利用者が満たせるものを開発してはいかがでしょうか。

※あなた自身が欲望に忠実になり闇落ちすると、悪質アフィリエイターやSEOを悪用したWELQ中の人のような存在になるので、健全な精神状態と倫理観は維持しましょう。

金稼ぎとは

ここでは投稿した作品に対する収益還元や作家への投げ銭が得られるサービスを紹介します。

これ以外にも、自分の小説やイラストを販売できるBOOTHのようなサービス、Youtubeのスーパーチャット・メンバーシップやFANBOXのようなクリエイター支援や投げ銭のサービスでもお金が稼げますので、サービス設計の参考にしてはいかがでしょうか。

Webサービス運営者は単発の課金であれ月額課金であれ自分の金もうけ(いかに無課金勢に課金させるか)第一で会議でも金や数字(KPIなど)の話しかしない人種なくせに、やれ「カスタマーエクスペリエンス」だとか「カスタマーサクセス」だとかぬかしおるのであほかと思います。

「利用者に収益を還元する」とか「生み出した作品の対価が得られる仕組みを提供する」とか、利用者にメリットがある仕組みの構築(+その結果としてのマネタイズ)ができてはじめてカスタマーについて語る資格があるのではないでしょうか。

広告収益の分配

作品のPVや閲覧人数などに応じて広告収益が分配される仕組みです。
Peing(質問箱)開発者のSeseriさんが作った小説投稿サイト「scraiv」がこの仕組みを導入しています。
他にもアルファポリス、カクヨム、ノベルバなどが同じ仕組みを導入していますね。

広告収入の分配といっても、作家の作品内にある広告リンクがGoogleアドセンスのようにクリックされたり、アフィリエイトのように商品が購入されたりすることで分配されるわけではありません。

それぞれのサービスで広告収益分配の基準は異なりますが、純粋に作品が読まれるほど収益が増える仕組みとなっています。

予約投稿を課金で先読み

読者が課金することで、作家が予約投稿した最新話を公開日前に読める仕組みを導入し、課金額の40%を作家に還元しています。

※最新話を無料で読みたいときは公開日まで待てばOK。

これを導入しているのは待ラノだけですね。

利用者による投げ銭

Youtubeのスーパーチャットのように、作者に対して読者が投げ銭を行える仕組みです。

投げ銭機能はノベルアッププラスやマグネットマクロリンク、カクヨムなどが実装しています。

コンテストの開催

優秀な作品は賞金がもらえたり書籍化したりといった特典があるWebコンテストをサイト内で開催し、作家がそれに応募できるようにする仕組みです。

カクヨムやアルファポリス、エブリスタなどがWebコンテストを定期的に開催しています。

欲求充足とは

ここからはマズローの欲求5段階説を用いて説明します。
詳しく知りたい方はfelletさんの下記記事をご覧ください。
マズローの欲求5段階説を図付きで解説!各段階に合わせたサービスも紹介

そして「金稼ぎ」で説明した仕組みは小説投稿サイトでいう作家向けのものですが、欲求充足に関しては作家読者双方の欲求(承認欲求とか作家・読者と繋がりたい欲求とかコミュニティに所属したい欲求など)を満たす仕組みとなっています。

そうしないと、金と欲を満たしたい作家しかおらず読者がいないという地獄のようなサービス(どれだけ作品投稿しようがPVもブックマークも感想も増えない)と化してしまうからです。

社会的欲求を満たす

集団への帰属や愛情を求める欲求です。
コミュニティへの所属や人とのつながりなどが得られることで満たされます。

これを満たす機能として、ノベルデイズのコラボノベル(他のユーザーと共同で作品を作る)機能やエブリスタのコミュニティ機能などが挙げられます。

また、ハーメルンには「捜索掲示板」という機能があり、自分が探している作品について質問すると他の利用者が返信で教えてくれるので、そこで作品について盛り上がることができます。

承認欲求を満たす

他人から尊敬されたい、認められたい、賞賛されたいという欲求です。
具体的には作品を読まれたい、読者に応援されたいなどです。

マグネットマクロリンクでは読者が作品を読んだり、作家が創作活動をすることによって自動的に「磁界」というポイントが貯まります。
作家は自分が獲得した磁界(ポイント)と読者からプレゼントされた磁界(ポイント)を消費することで、サイト内で自分の作品を宣伝することができます。

ノベルアッププラスでは、ログインするともらえるポイントを作家にプレゼントしたり、感想コメントや感想スタンプを送るなどで作家・作品を応援する仕組みがあります。

また、豊富なランキングやピックアップがあり、自分の作品が露出する機会が多いことも重要です。
例えばノベルアッププラスには新着作品・注目作品・読者のオススメ作品などのピックアップがありますし、ハーメルンには総合・短編・オリジナル(作品)・二次創作・R18など複数のランキングがあります。

カクヨムでは特集(カクヨム公式レビュアーが選んだ作品を紹介する記事)や各ジャンルの作品のピックアップが公開されており、多くの作品に日があたりやすくなっています。

自己実現欲求を満たす

自分の可能性を追求し、技術や能力の向上に努めて作家としての自分の理想像を実現したいという欲求です。

ツギクルには「人工知能を用いた文章解析システム」があり、AIが客観的に作品の分析をしてくれます。

また、ノベルアッププラスでは小説を投稿する、小説を読む、感想を書き込むなどにより自分のアカウントのレベルや称号がアップしていきます。

一番の悪手は「運営が多数の利用者を置き去りにして全力で金儲けしだすこと」

今回ご紹介した方法が正解ではありません。

多くの利用者を獲得する方法が他にもありますし、そこを研究していただけたらと思います。

ただし、「絶対にWebサービス運営者がやってはならない悪手」が存在します。

それは一部の利用者のみ優遇する運営の商品・広告を推しすぎることです。

一部の利用者のみ優遇するとは、小説投稿サイトで例えると「PVが稼げる作家の作品バナーをトップページの目立つ場所に掲載したり、書籍化のバックアップや(公式ブログ等で)販促支援を行う」「運営を通じてお仕事がもらえる」「PVが稼げる作家との打ち合わせと称して高級なご飯を奢る」などの運営が不自然なまでに特定利用者を推していたり癒着している状況を指します。
※実際に小説投稿サイト運営がこれらを行っていると言うわけではありません。

運営の商品・広告を推しすぎるとは、小説投稿サイトで例えると「トップページなどが書籍の広告などで溢れかえっていて利用者が使いづらい」状況です。
※アルファポリスがこの傾向にあります。

利用者が報われる仕組み、飽きさせない仕組みを作れ

報われる仕組みとは

また、悪手というよりは過疎らないためにやっておいた方がベターなのが「利用者が注目される機会をなるべく平等に与える」「利用者の努力が報われる」サービス設計をする事です。

小説投稿サイトで例えると、既存のランキング(PVやブックマークに基づくもの)だけでなく、活動歴が浅い作家のみのランキングや細かくジャンルわけしたランキングを用意したり、活動歴が長い作家の作品やニッチなジャンルでPVは高くないものの濃いファンがついている作品等を運営がピックアップして紹介するなどです。

ぶっちゃけると、小説投稿サイトYoutubeはてなブログも、何らかのコンテンツを投稿するプラットフォームのランキングっていうのは

ランキング上位勢がランキングにのることによってさらにPVやブクマを稼いでランキングにのり続けているのが現状です

そしてそんな状況で並み居る古参上位勢を押しのけてランキング上位に食い込めるのは「強力な個性や才能の持ち主」か「元々大多数のファンを抱えている人間(芸能人や他の小説サイトで固定ファンを抱えた作家など)」か「箱推しされている事務所に所属したり動画再生数や配信の同時接続人数が稼げる人とコラボする(VTuberのにじさんじやホロライブなど)」くらいです。

※お金や互助会の力で何とかしようとする人もいますが、それは多くのサービスで利用規約違反になるので割愛。

きっと、商業出版を果たしたつよつよ作家や自称有識者の読者は「ランキング出来ない人間の努力が足りないからだ。才能がないからだ。」と言うでしょう。
登録者10万人越えのYoutuber・VTuberもオンラインサロンを開いちゃうようなはてなブロガーも「そうだそうだ」と賛同するでしょう。

※その格差があるからこそアフィリエイトの情報商材やサロン、コンサル、事務所(○○ネットワークとか)に金を払う人が出てくるわけです。

それは利用者目線で言えば正しい事なのかもしれませんが、Webサービスの運営側としては、努力(作品のコンセプトを工夫する、毎日投稿する、コメント返しするなど)を続けてもランキング入りできず離脱する作家が増えて過疎化するリスクのケアを考えなければいけません。

あなたのWebサービスが世界でオンリーワンのものでないのなら、作家たちは競合他社の類似サービスに移っていくからです。

飽きさせない仕組みとは

そして、代わり映えしない作品(所謂なろう系の異世界転生ものとか)や作家で埋め尽くされたランキングに飽きて離脱する読者もケアしないと、作家と同様に競合他社の類似サービスに移っていかれます。

これへのケアも報われる仕組みと同じく、既存のものとは違うランキングの創設やピックアップで作品を掘り起こす仕組みが必要となります。

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

xvideosの動画をダウンロード その1

はじめに

今回はAVをダウンロードします。

pornhubは複雑だったのでxvideosの動画をダウンロードしようと思います。

ちょっと解析

まずは動画のURLをゲットすべく解析します。

$ wget "https://www.xvideos.com/video52179795/_~_~_" -O a.html
~~略~~
$ cat a.html|grep "http.*mp4"
<div id="html5video_base" style="display: none;"><div style="width: 100%; text-align: center;"><a href="https://cdn77-vid.xvideos-cdn.com/e6ieLBaypmMyelQJTIAYPQ==,1576064969/videos/mp4/e/a/e/xvideos.com_eae92b8f042cd0aad9e2900f90c43252.mp4?ui=MTEyLjY4LjE3OS45NS0vdmlkZW81MjE3OTc5NS9ffl9-Xw=="><img src="https://img-l3.xvideos-cdn.com/videos/thumbslll/ea/e9/2b/eae92b8f042cd0aad9e2900f90c43252/eae92b8f042cd0aad9e2900f90c43252.15.jpg" style="max-width:100%; height:auto;"></a></div><div style='width: 99%; text-align: center; font-size: 28px; border: 2px #333 solid; padding: 10px; display: block; background: #888;'><div style='font-weight: bold; padding: 3px; border: 1px #000 solid; background: #CCC;'><a href="https://cdn77-vid.xvideos-cdn.com/VVSwC8m7ljMHGpbbmk-DZg==,1576064969/videos/3gp/e/a/e/xvideos.com_eae92b8f042cd0aad9e2900f90c43252.mp4?ui=MTEyLjY4LjE3OS45NS0vdmlkZW81MjE3OTc5NS9ffl9-Xw==">View Low Qual</a></div><div style='font-weight: bold; padding: 3px; border: 1px #000 solid; margin-top: 10px; background: #CCC;'><a href="https://cdn77-vid.xvideos-cdn.com/e6ieLBaypmMyelQJTIAYPQ==,1576064969/videos/mp4/e/a/e/xvideos.com_eae92b8f042cd0aad9e2900f90c43252.mp4?ui=MTEyLjY4LjE3OS45NS0vdmlkZW81MjE3OTc5NS9ffl9-Xw==">View High Qual</a></div></div><br /><script>
        html5player.setVideoUrlLow('https://cdn77-vid.xvideos-cdn.com/VVSwC8m7ljMHGpbbmk-DZg==,1576064969/videos/3gp/e/a/e/xvideos.com_eae92b8f042cd0aad9e2900f90c43252.mp4?ui=MTEyLjY4LjE3OS45NS0vdmlkZW81MjE3OTc5NS9ffl9-Xw==');
        html5player.setVideoUrlHigh('https://cdn77-vid.xvideos-cdn.com/e6ieLBaypmMyelQJTIAYPQ==,1576064969/videos/mp4/e/a/e/xvideos.com_eae92b8f042cd0aad9e2900f90c43252.mp4?ui=MTEyLjY4LjE3OS45NS0vdmlkZW81MjE3OTc5NS9ffl9-Xw==');

それっぽいのが出てきました。

Pythonで抽出

URLがわかればこっちのもんです。

とりあえずURLを抽出するところまでPythonでやってみます。

htmlの入手にはrequestsを使いました。

xvideos.py
import sys
import requests
import re

try:
  url=sys.argv[1]
  body=requests.get(url)
except:
  exit()
for url in re.findall("http[^\"\']+\.mp4[^\"\']+",body.text):
  print(url)
$ python3 xvideos.py "https://www.xvideos.com/video52179795/_~_~_"
https://cdn77-vid.xvideos-cdn.com/M3DPGG1uE8PVTE-r3Za1Kg==,1576065831/videos/mp4/e/a/e/xvideos.com_eae92b8f042cd0aad9e2900f90c43252.mp4
https://cdn77-vid.xvideos-cdn.com/wi_Y89u_DuPPNvDFB7v_GQ==,1576065831/videos/3gp/e/a/e/xvideos.com_eae92b8f042cd0aad9e2900f90c43252.mp4
https://cdn77-vid.xvideos-cdn.com/M3DPGG1uE8PVTE-r3Za1Kg==,1576065831/videos/mp4/e/a/e/xvideos.com_eae92b8f042cd0aad9e2900f90c43252.mp4
https://cdn77-vid.xvideos-cdn.com/wi_Y89u_DuPPNvDFB7v_GQ==,1576065831/videos/3gp/e/a/e/xvideos.com_eae92b8f042cd0aad9e2900f90c43252.mp4
https://cdn77-vid.xvideos-cdn.com/M3DPGG1uE8PVTE-r3Za1Kg==,1576065831/videos/mp4/e/a/e/xvideos.com_eae92b8f042cd0aad9e2900f90c43252.mp4

ちゃんと出てきました。

動画をGETする

URLを取り出せたので、後は動画をGETするだけです。

URLが複数でできましたが、全部同じような感じだったので一番目のやつを使うことにします。

動画のGETもrequestsを使います。

動画なのでstreamモードという小技を使います。よくわかりませんが、イテレーターを返すみたいな感じで、動画の取得が早くなるらしいです。

xvideos.py
import sys
import requests
import re

try:
  url=sys.argv[1]
  body=requests.get(url)
except:
  exit()
url=re.search("http[^\"\']+\.mp4[^\"\']+",body.text)
if not url:
  exit()
url=url.group()
dname="videos/"
fname=re.search("[^/\.]+\.mp4",url).group()
print("directory :",dname,"filename :",fname)
with open(dname+fname,"wb") as f:
  for chunk in requests.get(url,stream=True):
    f.write(chunk)

いよいよ、動画を取得します。。。

$ mkdir videos
$ python3 xvideos.py https://www.xvideos.com/video52179795/_~_~_
directory : videos/ filename : xvideos.com_eae92b8f042cd0aad9e2900f90c43252.mp4
getting https://cdn77-vid.xvideos-cdn.com/HL9sfQdQCckONKcxgNV0SQ==,1576066626/videos/mp4/e/a/e/xvideos.com_eae92b8f042cd0aad9e2900f90c43252.mp4

結果は、お察しください。

まとめる

使いやすいようにこの関数をまとめます。

まとめてる最中にところどころ改良しました。

xvideos.py
def getvideo(url,dname="videos/",fname=""):
  try:
    body=requests.get(url)
  except:
    return False
  url=re.search("http[^\"\']+\.mp4[^\"\']+",body.text)
  if not url:
    return False
  url=url.group()
  if not fname:
    fname=re.search("[a-zA-Z0-9]+\.mp4",url).group()
  if dname[len(dname)-1]!='/':
    dname+='/'
  try:
    with open(dname+fname,"wb") as f:
      for chunk in requests.get(url,stream=True):
        f.write(chunk)
  except:
    return False
  return True

多分ちゃんと使えます。

終わりに

次回はもう少し高機能にしようと思います。

では、よい性生活を。

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

LINE WORKSボットをAmazon Lexで作る

LINEWORKS Advent Calendar 2019 11日目の記事です。

チャットボットを簡単に作れるサービスは数多く存在してますが、今回は前から気になってた「Amazon Lex」を使ったLINE WORKSボット開発をしてみました。

Amazon Lex とは

「Amazon Lex」は、会話型インターフェイスを提供するAWSサービスです。LexにはAlexaで使われている技術と同じものが使われているそう。
自然言語処理だけでなく音声認識も内包されてます。

https://aws.amazon.com/jp/lex/

似たようなサービスにDialogFlowとかAzure Bot ServiceとかIBM Watson Assistant

今回はAmazon Lexで対話フローを作成し、LINE WORKSのトーク上で会話できるボットを作りました。

!!!注意!!!

2019/12/11現在、Amazon Lexは米国英語のみ対応しており、日本語および東京リージョンでの利用は対応してません。
今回についても英語で対話するチャットボットとなります。(日本語対応いつになるか...)
また、使用するリージョンはオレゴンとしました。

構成

lex lineworks (2).png

  • AWS Lambdaを使ったサーバーレス構成とする。
  • LINE WORKSからのコールバックをAPI Gateway経由で受け取る。そこからAmazon Lexと連携して対話処理をする。
  • LINE WORKSのパラメータはSystems Managerパラメータストアで管理する。
  • LINE WORKSのアクセストークンは定期的に実行されるLambdaにより更新される。

開発環境

実装

1. Amazon LexでBotを作成

今回は、以下の公式のチュートリアルに沿ってサンプルを使ったBotを作成しました。

ボットの例: BookTrip - Amazon Lex https://docs.aws.amazon.com/ja_jp/lex/latest/dg/ex-book-trip.html

簡単に説明すると、車やホテルを予約するチャットボットです。
サンプルから「BookTrip」を選んで、作成しました。

lex bot book trip

このような画面で、インテントの設定や、対話フローについて設定をします。

他のチャットボット作成サービスを使ったことがある人ならすぐ使える印象。初心者はなかなか一から設定するのは大変そうだなと感じました。

2. LINE WORKS Developer Consoleから各種キー作成 & ボット作成

LINE WORKS Developer Consoleへログインし、キーの作成や今回のボットを作成します。

詳しくはこちらの過去記事を参照ください。

LINE WORKS トークBot をPythonで実装してみる 〜前編: API認証〜 https://qiita.com/mmclsntr/items/1d0f520f1df5dffea24b

3. LINE WORKSボットアプリサーバー作成

Lambdaで構成し、ランタイムをPython3.7で実装しました。

Lambda関数は以下の2つ

  1. LINE WORKS アクセストークン定期更新
    • CloudWatch Event スケジュールイベントで定期実行 (半日に一回)
  2. LINE WORKS チャットボット
    • API Gateway経由でLINE WORKSからのメッセージを取得し、Amazon Lex Botと連携。

以下、サンプルコード

lambda_function.py
import json
import jwt
import requests
import urllib
import boto3
import os
from datetime import datetime
from base64 import b64encode, b64decode
import hashlib
import hmac

from requests.structures import CaseInsensitiveDict

ssm = boto3.client('ssm')
lex = boto3.client('lex-runtime')

####################################
# Systems Manager パラメータストア #
####################################
def get_parameter(key):
    """
    SSMパラメータストアからパラメータ取得
    """
    response = ssm.get_parameters(
        Names=[
            key
        ],
        WithDecryption=True
    )
    parameters = response["Parameters"]
    if len(parameters) > 0:
        return response['Parameters'][0]["Value"]
    else:
        return ""


def put_parameter(key, value):
    """
    SSMパラメータストアへパラメータを格納
    """
    response = ssm.put_parameter(
        Name=key,
        Value=value,
        Type='SecureString',
        Overwrite=True
    )


##############
# Amazon Lex #
##############
def post_text_to_lex(text, user_id, bot_name, bot_alias):
    """
    Amazon Lexへテキストを送信 & 返答取得
    """
    response = lex.post_text(
        botName=bot_name,
        botAlias=bot_alias,
        userId=user_id,
        inputText=text
    )

    return response["message"]


##################
# LINE WORKS API #
##################
def get_jwt(server_list_id, server_list_privatekey):
    """
    LINE WORKS アクセストークンのためのJWT取得
    """
    current_time = datetime.now().timestamp()
    iss = server_list_id
    iat = current_time
    exp = current_time + (60 * 60) # 1時間

    secret = server_list_privatekey

    jwstoken = jwt.encode(
        {
            "iss": iss,
            "iat": iat,
            "exp": exp
        }, secret, algorithm="RS256")

    return jwstoken.decode('utf-8')


def get_server_token(api_id, jwttoken):
    """
    LINE WORKS アクセストークン取得
    """
    url = 'https://authapi.worksmobile.com/b/{}/server/token'.format(api_id)

    headers = {
        'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8'
    }

    params = {
        "grant_type" : urllib.parse.quote("urn:ietf:params:oauth:grant-type:jwt-bearer"),
        "assertion" : jwttoken
    }

    form_data = params

    r = requests.post(url=url, data=form_data, headers=headers)

    body = json.loads(r.text)
    access_token = body["access_token"]

    return access_token


def validate_request(body, signature, api_id):
    """
    LINE WORKS リクエスト検証
    """
    # API IDを秘密鍵に利用
    secretKey = api_id.encode()
    payload = body.encode()

    # HMAC-SHA256 アルゴリズムでエンコード
    encoded_body = hmac.new(secretKey, payload, hashlib.sha256).digest()
    # BASE64 エンコード
    encoded_b64_body = b64encode(encoded_body).decode()

    # 比較
    return encoded_b64_body == signature


def send_message(content, api_id, botno, consumer_key, access_token, account_id):
    """
    LINE WORKS メッセージ送信
    """
    url = 'https://apis.worksmobile.com/{}/message/sendMessage/v2'.format(api_id)

    headers = {
          'Content-Type' : 'application/json;charset=UTF-8',
          'consumerKey' : consumer_key,
          'Authorization' : "Bearer " + access_token
        }

    params = {
            "botNo" : int(botno),
            "accountId" : account_id,
            "content" : content
        }

    form_data = json.dumps(params)

    r = requests.post(url=url, data=form_data, headers=headers)

    if r.status_code == 200:
        return True

    return False

######################
# Lambda関数ハンドラ #
######################
def update_token_handler(event, context):
    """
    LINE WORKS アクセストークン定期更新 Lambdaハンドラー関数
    """
    # SSMパラメータストアからLINE WORKSのパラメータを取得
    api_id = get_parameter("lw_api_id")
    server_list_id = get_parameter("lw_server_list_id")
    server_list_privatekey = get_parameter("lw_server_list_private_key").replace("\\n", "\n")
    # JWT取得
    jwttoken = get_jwt(server_list_id, server_list_privatekey)

    # Server token取得
    access_token = get_server_token(api_id, jwttoken)

    # Access Tokenをパラメータストアに設定
    put_parameter("lw_access_token", access_token)

    return


def chat_with_lex_handler(event, content):
    """
    LINE WORKS チャットボット Lambdaハンドラー関数
    """
    botno = os.environ.get("BOTNO")
    lex_bot_name = os.environ.get("LEX_BOT_NAME")
    lex_bot_alias = os.environ.get("LEX_BOT_ALIAS")
    # SSMパラメータストアからLINE WORKSのパラメータを取得
    api_id = get_parameter("lw_api_id")
    consumer_key = get_parameter("lw_server_api_consumer_key")
    access_token = get_parameter("lw_access_token")

    event = CaseInsensitiveDict(event)
    headers = event["headers"]
    body = event["body"]

    # リクエスト検証
    if not validate_request(body, headers.get("x-works-signature"), api_id):
        # 不正なリクエスト
        return

    # Jsonへパース
    request = json.loads(body)

    # 送信ユーザー取得
    account_id = request["source"]["accountId"]

    res_content = {
        "type" : "text",
        "text" : "Only text"
    }

    # 受信したメッセージの中身を確認
    request_type = request["type"]
    ## Message
    if request_type == "message":
        content = request["content"]
        content_type = content["type"]
        ## Text
        if content_type == "text":
            text = content["text"]

            # Amazon Lexと連携
            reply_txt = post_text_to_lex(text, account_id.replace("@", "a"), lex_bot_name, lex_bot_alias)

            res_content = {
                "type" : "text",
                "text" : reply_txt
            }

    # 送信
    rst = send_message(res_content, api_id, botno, consumer_key, access_token, account_id)

    res_body = {
        "code": 200,
        "message": "OK"
    }
    response = {
        "statusCode": 200,
        "headers": {
            "Content-Type": "application/json"
        },
        "body": json.dumps(res_body)
    }

    return response

こちらの過去記事もご参照ください。
LINE WORKS トークBot をPythonで実装してみる 〜後編: チャットボット実装〜 https://qiita.com/mmclsntr/items/28ba6baaf23124a53663

ソースコード

https://github.com/mmclsntr/lineworks-bot-with-amazon-lex

動かしてみる

以下の感じで、LINE WORKSのトーク上で、Lexで作成したチャットボットと (英語で) 会話できます。
LINE WORKS Lex

まとめ

Amazon Lexで作成したチャットボットもLINE WORKSで問題なく動かせることができました。
簡単な問い合わせであれば楽に実現できるかなと思います。ぜひ日本語対応していただけると。。

あとはLexで対話の設定をいろいろチューニングして遊んでみようと思います。

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

Pythonで好きな場所のファイルをimportする方法

条件

Pythonのバージョンによって?できるものとできないものがあるようです。
いくつか例をあげたので、もし失敗したら他の方法を試してみてください。

フォルダの構成

├─python
| main.py
|
| ├─code
| | |mnist.py
|
| ├─dataset
| | |activation_function.py

やりたいことリスト

  1. main.py内でmnist.pyimportしたい!!
  2. mnist.py内でactivation_function.pyimportしたい!!
  3. mnist.py内でmain.pyimportしたい!!

やりたいこと1

main.py内でmnist.pyimportしたい!!

方法1

main.py
import sys
sys.path.append("code")
import mnist

方法2

Pythonの自作モジュールをimportしたいならsys.pathを設定しよう」を参考にさせていただきました。
自分の環境では動作しませんでした。環境によっては動作するかもしれません。

main.py
import sys
sys.path.append("python/code")
import mnist

やりたいこと2

mnist.py内でactivation_function.pyimportしたい!!

方法1

ゼロから作るDeepLearningを参考にしました。
自分の環境では動作しませんでした。環境によっては動作するかもしれません。

mnist.py
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import dataset.activation_function

方法2

mnist.py
import sys
sys.path.append("")
# sys.path.append("./")でも可能
# sys.path.append(".")でも可能
import dataset.activation_function

方法3

mnist.py
import sys
sys.path.append("dataset")
import activation_function #ちょっと綺麗になる

やりたいこと3

mnist.py内でmain.pyimportしたい!!

これはわかりませんでした!!可能であるのならば教えてください!!
まず下のファイルから上のファイルをimportするのはフォルダーの構造として駄目な気がするので、フォルダー構成を見直せ!ということなのかもしれません。
ちなみに以下のことを試してみましたが自分の環境では、動作しませんでした。

mnist.py
import sys
sys.path.append("../")
import main

おわり

紹介した多くの方法がPython2系では動かないと思います。
Python3を使っておきましょう
いくつか動かないのはおそらく親フォルダーの扱いの差だと思うのですが、これはOS(MacとWindows)の差なのかな?あとで検証するかもです。

タイトルの「ファイル」の部分が適切では無い気がしますので、それに置き換わる何かがあればコメントで教えてくださいm(_ _)m

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

英文PDFをまとめて日本語化

PDFをまとめて翻訳したい

PDFを翻訳したい場合、Google翻訳の標準機能がまず思い当たりますが、ファイルサイズの上限があって不便です。
前回の記事ではGoogle Translate APIを叩いて英文を日本語化しました。読みたい文章がPDFの場合にはGoogleドキュメントやAdobe Acrobatを使ってテキストを取り出すことが考えられますが、工程数が多い難点があります。ここもPDF Minerというライブラリを使うことでPythonにやらせることができそうです。
以下の記事を大変参考にさせていただきました。
【PDFMiner】PDFからテキストの抽出

作成したスクリプトは以下にあります。
https://github.com/KanikaniYou/translate_pdf

あるフォルダ内にある全てのPDFについて、抽出・翻訳・テキストファイルとして出力します。

PDFMinerでの取り出し後に要らない文字列まで取ってしまうので("......"と同じ記号が続く文字列など。目次とかにあるよね。)、抽出後のテキストファイルを中間ファイルとしてまとめておき、人の手で要らない部分を取ってやることができるようにしています。

翻訳作業の全体の流れとしては
0. クイックスタート | Google Cloud Translation API Documentation | Google Cloud Platformを参考にGoogle Translate APIを取得
1. PDFからテキストを取り出してテキストファイルとして保存する(pdf_to_txt.py)
2. テキストを整形して一行の新しいテキストファイルとして保存する(let_translatable.py)
3. 英文を日本語化してテキストファイルとして保存する(translate_en_jp.py)

となります。先述の通り、1.のあとで人手でテキストファイルを見てやることで、PDF Minerで抽出したテキストをチェックし、Google Translate APIを無駄に叩かなくて済むように必要箇所だけ取り出すということができます。

環境

Python3系の入ってるLinuxならいけると思います。手元の環境は Cloud9でUbuntu 18.04です。

pip install pdfminer.six

ちなみにPDF Minerはとても有用なのですが、日本語などを取り出したい時に文字化けが起こりやすいようです。今回は英語を取り出すのでそこまで頻繁に問題は起きないと思います。

PDF Minerでの日本語取り出しに関する既知の不具合: Still have issues with CID Characters #39

git clone https://github.com/KanikaniYou/translate_pdf
cd translate_pdf

ファイル構成です。(説明上、翻訳したいPDF10個をすでに置いてあります。)

.
├── eng_txt
├── eng_txt_split
├── jpn_txt
├── let_translatable.py
├── pdf_source
│   ├── report_1.pdf
│   ├── report_10.pdf
│   ├── report_2.pdf
│   ├── report_3.pdf
│   ├── report_4.pdf
│   ├── report_5.pdf
│   ├── report_6.pdf
│   ├── report_7.pdf
│   ├── report_8.pdf
│   └── report_9.pdf
├── pdf_to_txt.py
└── translate_en_jp.py

スクリーンショット 2019-12-11 15.07.11.png

1.テキストの取り出し

pdf_to_txt.py
import sys

from pdfminer.converter import PDFPageAggregator
from pdfminer.layout import LAParams, LTContainer, LTTextBox
from pdfminer.pdfinterp import PDFPageInterpreter, PDFResourceManager
from pdfminer.pdfpage import PDFPage

import os
import re

def find_textboxes_recursively(layout_obj):
    if isinstance(layout_obj, LTTextBox):
        return [layout_obj]

    if isinstance(layout_obj, LTContainer):
        boxes = []
        for child in layout_obj:
            boxes.extend(find_textboxes_recursively(child))
        return boxes
    return[]

def pdf_read_controller(filepath):
    try:
        text_in_pdf = ""

        with open(filepath, 'rb') as f:

            for page in PDFPage.get_pages(f):
                try:

                    interpreter.process_page(page)
                    layout = device.get_result()

                    boxes = find_textboxes_recursively(layout)
                    boxes.sort(key=lambda b:(-b.y1, b.x0))

                    text_in_page = ""
                    for box in boxes:
                        text_in_box = ""

                        text_in_box += box.get_text().strip().strip(" ")

                        text_in_box.rstrip("\n")
                        text_in_box = re.sub(r'  ', " ", text_in_box)

                        text_in_page += text_in_box
                    text_in_pdf += text_in_page
                except Exception as e:
                    print(e)

        return(text_in_pdf)

    except Exception as e:
        print(e)
        print("error: " + filepath)
        return("no-text")


def make_txtfile(folder_path,file_name,text='error'):
    if text != "no-text":
        with open(folder_path+"/"+file_name, mode='w') as f:
            f.write(text)


laparams = LAParams(detect_vertical=True)
resource_manager = PDFResourceManager()
device = PDFPageAggregator(resource_manager, laparams=laparams)
interpreter = PDFPageInterpreter(resource_manager, device)

if __name__ == '__main__':
    for file_name in os.listdir("pdf_source"):
        if file_name.endswith(".pdf"):
            print(file_name)
            text_in_page = pdf_read_controller("pdf_source/" + file_name)
            make_txtfile("eng_txt_split",file_name.rstrip("pdf")+"txt",text_in_page)

フォルダ「pdf_source」下のpdfを全部読んでテキストファイルを作って出力します。

 $ python pdf_to_txt.py
report_3.pdf
report_7.pdf
report_2.pdf
report_1.pdf
unpack requires a buffer of 10 bytes
unpack requires a buffer of 8 bytes
report_5.pdf
report_9.pdf
report_8.pdf
unpack requires a buffer of 6 bytes
unpack requires a buffer of 6 bytes
unpack requires a buffer of 4 bytes
report_4.pdf
report_6.pdf
report_10.pdf

いくつかエラーが出ていますが、無視してテキストファイルを作成します。PDFはややこしいですね。
似たようなエラー:struct.error: unpack requires a string argument of length 16

1-2.テキストの目視チェック(しなくてもOK)

スクリーンショット 2019-12-11 16.34.43.png

例えばテキストの一部にこういった箇所が含まれていました。Google Translate APIを無駄に叩きたくないので要らないところは削除してやりましょう。

2.テキストの整形

let_translatable.py
import os

if __name__ == '__main__':
    for file_name in os.listdir("eng_txt_split"):
        if file_name.endswith(".txt"):
            print(file_name)
            text = ""
            with open("eng_txt_split/"+file_name) as f:
                l = f.readlines()
                for line in l:
                    text += str(line).rstrip('\n')

            path_w = "eng_txt/" + file_name
            with open(path_w, mode='w') as f:
                f.write(text)

PDF Minerで出てくるテキストは改行だらけで、このままGoogle翻訳に入れると上手く翻訳してくれそうにありません。そこで、改行を無くしたテキストファイルを新しく作成し、フォルダ eng_txt_split に出力してやります。

$ python let_translatable.py
report_4.txt
report_10.txt
report_2.txt
report_6.txt
report_9.txt
report_5.txt
report_8.txt
report_7.txt
report_3.txt
report_1.txt

3.英語→日本語に翻訳!

できたテキストをいよいよ翻訳します。中身は前記事を参考にしていただければと思います。

translate_en_jp.py
import requests
import json
import os
import re
import time

API_key = '<ここにAPIキーを入れてください>'
def post_text(text):
    url_items = 'https://www.googleapis.com/language/translate/v2'
    item_data = {
        'target': 'ja',
        'source': 'en',
        'q':text
    }
    response = requests.post('https://www.googleapis.com/language/translate/v2?key={}'.format(API_key), data=item_data)
    return response.text

def jsonConversion(jsonStr):
    data = json.loads(jsonStr)
    return data["data"]["translations"][0]["translatedText"]

def split_text(text):
    sen_list = text.split('.')

    to_google_sen = ""
    from_google = ""

    for index, sen in enumerate(sen_list[:-1]):
        to_google_sen += sen + '. '
        if len(to_google_sen)>1000:
            from_google += jsonConversion(post_text(to_google_sen)) +'\n'
            time.sleep(1)

            to_google_sen = ""
        if index == len(sen_list)-2:
            from_google += jsonConversion(post_text(to_google_sen))
            time.sleep(1)
    return from_google


if __name__ == '__main__':
    for file_name in os.listdir("eng_txt"):
        print("source: " + file_name)
        with open("eng_txt/"+file_name) as f:
            s = f.read()
            new_text = split_text(s)
            path_w = "jpn_txt/" + file_name
            with open(path_w, mode='w') as f:
                f.write(new_text)
 $ python translate_en_jp.py
source: report_4.txt
source: report_10.txt
source: report_2.txt
source: report_6.txt
source: report_9.txt
source: report_5.txt
source: report_8.txt
source: report_7.txt
source: report_3.txt
source: report_1.txt

長いテキストだとちょっと時間がかかります。

成果物

jpn_txtフォルダ内に翻訳後のテキストファイルが入ります。
スクリーンショット 2019-12-11 17.04.05.png

これで英文PDFに悩まされずに済みそうですね!
もっとも、これで出力されるテキストはレイアウトなんて概念はなくなってますし、ページ間などでは上手く翻訳されないこともあると思います。本来はその辺の処理もできればいいんでしょうけど、なかなか難しそうです。あくまでたくさんあるPDFについて日本語で目を通したいという際に使って頂ければと思います。

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

manylinux wheelのビルドにcircleciを使わせていただいている話

manylinux wheelとは

manylinuxという「大体すべてのLinuxディストリで動く」wheelがあります。
wheelというのはコンパイル済みのネイティブライブラリを含むPythonパッケージです。
有名どころではtensorflowがこのmanylinux wheelを配布していたりします。
このmanylinux wheelのビルドにcircleciが最適だったので紹介します。

なぜ manylinux wheel の作成に circleciが適しているか

  • CIサービス内でArtifact(私の場合wheel)の保存ができる

ため私はcircleciでmanylinux wheelのビルドを行っています。

具体的には https://github.com/ecell/ecell4_base/blob/master/.circleci/config.yml のように行っています。

yml中では

- store_artifacts:
  path: /root/circle/wheelhouse

がそれを行っている箇所になります。
私が知る限りtravisci ではこれを行うことができずcircleciを使わせていただいている次第です。

manylinuxのwheelは「大体すべてのLinuxで動く」というものなので
この環境中での動作テストだけでは不十分で他の複数のディストリでの動作も見たかったりします。

docker:
  - image: quay.io/pypa/manylinux2010_x86_64

はCentOS6です。(なぜそんな古いバージョンなのかはここでは割愛します。)

なのでとりあえずパッケージを置いておく場所がほしかったりします。
そこでこのArtifactの保存の機能が助かるんですね。

おわりに

昨日 【大阪】CircleCI ユーザーコミュニティミートアップ#2 でCircleCI Japanコミュニティの皆様にお世話になり本記事を書きたいと思うに至りました。
コミュニティって重要ですね。

以上。

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

Python(Flask)のプロジェクトで途中からLinterを導入しようとした話

話題

  • 参画しているプロジェクトのバックエンド(Flask)のコードがLinterやformatterが導入されておらず苦労した(なお継続中)話

希望

  • Githookでlint&formatしたくね??????

問題

  • プロジェクト初期に書かれたコードがpep8ガン無視でlint導入したらエラー出まくる
  • 一気にautofixかけたいけど、差分が出過ぎてレビューができない。
  • 全量リファクタもしたいが工数もないし、テストコードも十分じゃなさそう

妥協

  • 新規作成するコードはLint&formatかける。
  • 既存コードは一旦そのまま
  • 既存コードのテストコードを見直し、追加実装していつでもリファクタできるようにしておく
  • hookでLintはかける。formatはにんげんが判断してかける(苦肉の策)。
  • 既存はgit commit --no-verifyで無理矢理コミットする

開発方法

新規の場合

  1. コーディングする
  2. コミット時にLintが走る
  3. formaterをかける black hogehoge.py
  4. 手で直さなきゃならないとこはエラーがなくなるまで修正してコミット(ファイル指定でlintflake8 hogehoge.py)
  5. pushしてプルリクレビューへ

既存コードに手に入れる場合

  1. コーディングする
  2. コミット時にLintが走る
  3. エラーがあれば、black hogehoge.py --diffで追記部分のエラーを確認して手で直す。(ファイル指定でlintflake8 hogehoge.py)
  4. コミットするときはgit commit --no-verifyでhookを飛ばす
  5. pushしてプルリクレビューへ

Linterの設定

linterはflake8を利用する。
1. pip install flake8
2. 設定ファイルを作成(プロジェクトルートに.flake8を作る)

[flake8]
ignore = E203, E266, E501, W503, F403, F401
max-line-length = 79
max-complexity = 18
select = B,C,E,F,W,T4,B9

※flake8の設定は適宜変えること

  • 正直現代でmax-line-length<80は少なすぎ
  • igonoreはblackとの関係でE203,W503,W504は必須。それ以外は適宜
  • ここ見て

formatterの設定

formatterはblackを使う
1. pip install black
2. 設定ファイルを作成(プロジェクトルートにpyproject.tomlを作る)

pyproject.toml
[tool.black]
line-length = 79
include = '\.pyi?$'
exclude = '''
/(
    \.git
)
'''

flake8と合わせること

  • たまにformaterかけたのにエラーになるときあってびっくりする

githookの設定

hookの設定にはpre-commit使います。
1. pip install pre-commit
2. 設定ファイルの作成(プロジェクトルートに.pre-commit-config.yamlを設置)

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: flake8

まとめ

  • ツールの導入期間なこともあっていびつで手作業が含まれてしまってる。ちょっとずつ変えていく
  • linterやformaterは最初に入れる。ルールも最低限のもの(pep8)入れるべき
  • 書きづらいなと思った段階でルールを変えていく
  • テストコードがちゃんとしてれば既存いじっても基本は問題ない
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Worker Threadデザインパターンでスマートメーター計測ロガー

はじめに

かなり前からスマートメーターの電力情報を取得することができていますが、UDP通信であることや微弱な電波であるせいか長期間の安定的な運用が難しいとの指摘もあります。

参考:スマートメーターの情報を最安ハードウェアで引っこ抜く

長期間運用できる安定性と、様々な情報ソースに対応する拡張性をもった計測ロガーについて考えてみました。考え方としては、

  • Worker Thread デザインパターンによって、機能ごとの処理を互いに分離する
  • これにより、機能ごとの再起動など柔軟に対処でき、安定性が高まる
  • また、さまざまな機能を持ったクライアント、ワーカーを追加でき、機張性が高まる

というものです。

注) このプログラムは現状 WiSun モジュール RL7023 Stick-D/DSS(デュアルスタック)にしか対応していません。
ただ、デバイスドライバ部を少し修正すれば対応できると思います。

Worker Thread デザインパターンによる実装

やりたいことは、

  • スマートメータのデータを取得する
  • データをファイルに保存する

の2つなので、前者をクライアントスレッド、後者をワーカースレッド、保存するデータをリクエストとして捉えれば、Worker Thread デザインパターンで設計できそうです。Pythonのスレッドは並列処理によるパフォーマンス改善は望めないそうですが、今回の場合処理のほとんどがIOなので、機能を整理してわかりやすくすることと、拡張性、安定性の向上が目的となります。

なお、Arduinoからのセンサ情報を記録したいということが開発のきっかけとなっているので、クライアントとしてシリアルポートからのデータを読み取るクラスも実装しています。

今のところ、クライアントもワーカーも管理上区別する必要がないので、共通の Worker クラスを定義し、これを継承してスマートメーター通信とファイル保存のクラスを定義することにしました。
Workerクラスで定義されているのは、管理スレッドから停止の命令をうけつけ、stop イベントをセットする stop() 関数だけです。スレッドループでは stop イベントがセットされていない限りループを繰り返し、stop イベントがセットされていたら、ループを抜けて終了処理を行い、スレッドを終了する流れになります。stop() 関数はメインスレッドから呼び出されるので、join() を実行してWorkerのスレッドが終了するまでここで待機します。

worker.py
import threading

class Worker ( threading.Thread ):
    """Woker はスレッドを停止するためのイベントを設定するための抽象クラス。
    """
    def __init__( self ):
        super().__init__()
        self.stopEvent = threading.Event()

    def stop ( self ):
        """ストップイベントをセットする。
        """
        self.stopEvent.set()
        # スレッドが終了するまで待機
        self.join()

    def run(self):
        """ストップイベントがセットされていない限り、繰り返し実行する。
        この関数はオーバーライドする。
        """
        while not self.stopEvent.is_set():
            # do something
            pass

        # ここに終了処理を書く

Worker を継承した BrouteReader、FileRecorder、SerialReader クラスについての解説はここでは省略します。機能の概要は後述しますが、詳細はソースコードのコメントを参照してください。

メインの処理は2部構成で、

  • 動作する Worker を定義する設定部
  • 設定に従ってオブジェクトを作成しスレッドを開始する実行部

から成り立っています。

設定部 keiconf.py は以下のようなコードになります。

keiconf.py
import queue
from keilib.recorder import FileRecorder
from keilib.broute   import BrouteReader

# オブジェクト(スレッド)間で通信を行うための Queue
record_que = queue.Queue(50)

# 動作させるワーカオブジェクトの構成
worker_def = [
    {
        'class': FileRecorder,                  # FileRecorderオブジェクトを作成
        'args': {                               # 引数
            'fname_base': 'mydatafile',         # 記録ファイルの名前に使われる文字列
            'record_que': record_que            # 記録するデータをやり取りする Queue
        }
    },
    {
        'class': BrouteReader,                  # BrouteReaderオブジェクトを作成
        'args': {                               # 引数
            'port': '/dev/serial/by-id/usb-FTDI_FT230X_Basic_UART_xxxxxxxx-if00-port0',
                                                # WiSUNドングルのシリアルポート
            'baudrate': 115200,                 # WiSUNドングルのボーレート
            'broute_id': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
                                                # BルートID(電力会社に申請)
            'broute_pwd': 'xxxxxxxxxxxx',       # Bルートパスワード(電力会社に申請)
            'requests':[                        # 取得するプロパティ値の定義
                { 'epc':['D3','D7','E1'], 'cycle': 3600 },
                                                #   積算電力量用 係数(D3),有効桁数(D7),単位(E1),3600秒ごと
                { 'epc':['E7'], 'cycle': 10 },  #   瞬時電力(E7),10秒ごと
                { 'epc':['E0','E3'], 'cycle': 300 },
                                                #   積算電力量(E0,E3),300秒ごと
            ],
            'record_que': record_que            # 記録するデータをやり取りする Queue
        }
    },
]

ここでは、worker_defリストに FileRecorderとBrouteReaderの2つのクラスがコンストラクタへの引数の構成とともに定義されています。 実行部ではここに定義されているクラスのインスタンスを作成し管理します。作成されたオブジェクトはそれぞれ別のスレッドで動作を開始しますが、スレッド間でデータを受け渡すためのチャンネルとしてqueue.Queueオブジェクトを利用しています。record_queは2つのオブジェクト間でファイルに保存するデータを受け渡すために共有されています。BrouteReaderはスマートメーターから情報を得ると体裁を整えて record_queに投入(put)します。一方FileRecorderはrecoed_queに流れてきたデータを取得(get)してファイルに保存します。Queue オブジェクトはスレッドセーフであるため、排他制御を意識せずに使用できます。

実行部 key.py の核心部は以下のコードです。

kei.py
# Launch each worker instance(各ワーカーインスタンスの起動)
for wdef in worker_def:
    wdef['instance'] = wdef['class']( **wdef['args'] ) # インスタンスの作成
    wdef['instance'].start() # スレッドの開始

# Check if the threads have stopped at intervals, and restart them if stopped.
# 一定間隔でスレッドが停止したかどうかを確認し、停止していた場合は再起動
while True:
    for wdef in worker_def:
        if not wdef['instance'].isAlive():
            wdef['instance'] = wdef['class']( ** wdef['args'])
            wdef['instance'].start()

    time.sleep(10)

このようにメインスレッド内で、ワーカースレッドが不慮のエラーでストールした場合、再起動する仕組みを入れておくことによって長期間の安定動作が可能になります。
このほか kei.py にはログファイルの処理や、終了処理のためのシグナルハンドラの設定などが含まれています。詳細はソースコードを確認してください。

ソースコード https://github.com/kjmat/keilog

特徴

  • Raspberry Pi + Python3 で動作

    • Raspbian Lite (デスクトップが入ってない。ヘッドレスで運用)
    • SDカード 4G以上(データ量に応じて。16Gもあれば十分)
    • ネットワーク接続(時刻同期のためとかいろいろ)
    • python3, python3-serial, python3-requests
    • (最新ディストリビューションだと serial だけ別途インストール)
    • ラズパイでなくても大丈夫だと思いますが、前提としてつけっぱなしになります。
  • スマートメーターBルートデータ取得機能

    • WiSun モジュール RL7023 Stick-D/DSS(デュアルスタック)に対応
    • (シングルスタックの RL7023 Stick-D/IPS を使うには修正が必要。持ってないので未実装です)
    • 取得したいスマートメーターのプロパティと取得間隔を定義できる。
      対応プロパティ = D3,D7,E0,E1,E3,E7,E8,(EA,EB)
      • D3: 係数「積算電力量計測値」を実使用量に換算する係数
      • E7: 積算電力量計測値の有効桁数
      • E1: 単位「積算電力量計測値」の単位 (乗率)
      • E0: 積算電力量 計測値 (正方向計測値)
      • E3: 積算電力量 計測値 (逆方向計測値)
      • E7: 瞬時電力計測値(逆潮流のときは負の値)
      • E8: 瞬時電流計測値(T/R相別の電流)
      • EA: 定時 積算電力量 計測値 (正方向計測値)
      • EB: 定時 積算電力量 計測値 (逆方向計測値)
    • 状態遷移による振る舞いの管理 → 接続が切れても自動的に再接続
  • シリアルポートからのデータ取得機能

    • USBに接続した Arduino などの周辺機器から入力されたデータも記録可能
    • Arduinoはセンサのライブラリや作例が豊富で使いやすいし消費電力も少ない
    • XbeeやTWELiteDIPなどで無線化すれば、離れた場所のセンサも記録できる
    • TWELiteDIPにセンサを直結した場合、電池で数年持つセンサノードが作れる
  • リモートの Http サーバーにデータを POST する機能

    • 遠隔地に置いたラズパイのデータを(そこそこ)リアルタイムに取得するため
    • Raspi + Soracom(SIM) + AK020(3Gモデム) で安定動作
  • マルチスレッド(シンプルなフレームワーク)

    • 不慮のエラーによる停止からの回復、長期連続運用が可能
    • 機能の追加が容易

起動方法

Raspbian Lite にあらかじめ pyserial をインストールしておきます。

$ sudo apt install python3-serial

適当な作業ディレクトリを作成し、以下のようにファイルを配置し、keiconf.py には構成を定義しておきます。

workdir/
    |-- keilib/
    |   |-- __init__.py
    |   |-- broute.py
    |   +-- ....
    |
    |-- keiconf.py
    +-- kei.py

プログラムの実行は次のように行います。

$ python3 kei.py

実行すると workdir/ ディレクトリ内に、計測データを保存するファイルが2つと、プログラムの実行時の情報を出力するログファイルkei.logが作成されます。ログについては、

$ DEBUG=0 python3 kei.py

のように起動すると、ログレベル = DEBUG となりログの出力先が標準出力になります。ログレベルは USR1 シグナルを受け取ると INFO <-> DEBUG で反転します。
プログラムの終了については、HUP, INT, TERM シグナルによって各オブジェクトにストップイベントを送っています。ストップイベントを受けとったオブジェクトのスレッドはリソースを開放してから終了します。

出力ファイルの形式

2つのファイルが作られます。

1. [YYYYMMDD]-[mydatafile].txt
2. sum[YYYYMMDD]-[mydatafile].txt

1.は1行につき1件のデータが記録されており、行のフォーマットは以下の形をとります。

[YYYY/MM/DD hh:mm:ss],[UnitID],[SensorID],[Value],[DataID]<改行>

なお BrouteReader の場合、出力するデータは以下の通りです。

[UnitID]    = BR(固定)
[SensorID]  = スマートメーターのプロパティコード(EPC): E7, E0 等
[Value]     = 測定値(数値)
[DataID]    = x(固定)

2.は1行に各センサーの10分ごとの平均値が記録されます。

[YYYY/MM/DD hh:m0],[UnitID],[SensorID],[AverageValue]<改行>

日付のフォーマットに秒がない点に注意です。

参考

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