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

LeetCodeに毎日挑戦してみた 20. Valid Parentheses(Python、Go)

はじめに

無料英単語サイトE-tanを運営中の@ishishowです。

プログラマとしての能力を上げるために毎日leetcodeに取り組み、自分なりの解き方を挙げていきたいと思います。

Leetcodeとは

leetcode.com
ソフトウェア開発職のコーディング面接の練習といえばこれらしいです。
合計1500問以上のコーデイング問題が投稿されていて、実際の面接でも同じ問題が出されることは多いらしいとのことです。

Go言語入門+アルゴリズム脳の強化のためにGolangとPythonで解いていこうと思います。(Pythonは弱弱だが経験あり)

6問目(問題20)

20. Valid Parentheses

  • 問題内容(日本語訳)

文字列を考えるsだけで文字を含む'('')''{''}''['']'、入力文字列が有効であるかどうかを決定。

入力文字列は、次の場合に有効です。

  1. 開いたブラケットは、同じタイプのブラケットで閉じる必要があります。
  2. 開いたブラケットは正しい順序で閉じる必要があります。

Example 1:

  Input: s = "()"
  Output: true

Example 2:

  Input: s = "()[]{}"
  Output: true

Example 3:

  Input: s = "(]"
  Output: false

Example 4:

  Input: s = "([)]"
  Output: false

Example 5:

  Input: s = "{[]}"
  Output: true

考え方

  1. stack配列と辞書(map)を用意する。
  2. mapには対応する記号を入力
  3. 文字列を一文字ずつ見ていき、括弧の始まりならstackに追加し、閉じ括弧ならstackの直近のものを取り出して合っているかどうか確認。

説明

  1. stackとdict(map)を定義する。
  2. dict = {"]":"[", "}":"{", ")":"("}
  3. for文で文字を一文字ずつみていきます。
  • 解答コード
class Solution:
    def isValid(self, s):
        stack = []
        dict = {"]":"[", "}":"{", ")":"("}
        for char in s:
            if char in dict.values():
                stack.append(char)
            elif char in dict.keys():
                if stack == [] or dict[char] != stack.pop():
                    return False
            else:
                return False
        return stack == []


​ if char in dict.values(): で括弧始まりかどうか

​ elif char in dict.keys():  で括弧終わりかどうか

​ popで最新のstackの文字を取得

​ appendでstackに代入です。

  • Goでも書いてみます!
  func isValid(s string) bool {
    stack := make([]rune, 0)
    m := map[rune]rune{
        ')': '(',
        ']': '[',
        '}': '{',
    }
    for _, c := range s {
        switch c {
        case '(', '{', '[':
            stack = append(stack, c)
        case ')', '}', ']':
            if len(stack) == 0 || stack[len(stack)-1] != m[c] {
                return false
            }
            stack = stack[:len(stack)-1]
        }
    }

    return len(stack) == 0
  }

こちらのコードは少し難しいですが、Goで文字列を一文字ずつ見るためにこのコードになりました。

​ for _, c := range s のループ処理では文字列stringsを一文字ずつ読み取ります。その時cはrune型になるのでmapやスタックもrune型で定義しました。

せっかくGolang書いてるからswitich文で処理を書きました。

  • 自分メモ(Go)

文字列を一文字ずつ見るとrune

スライスにappend(固定長でないのでok)

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

AtCoder Beginner Contest 184 参戦記

AtCoder Beginner Contest 184 参戦記

ABC でC問題解けなかったの初めて…….

ABC184A - Determinant

1分で突破. 書くだけ.

a, b = map(int, input().split())
c, d = map(int, input().split())

print(a * d - b * c)

ABC184B - Quizzes

3分で突破. 書くだけ.

N, X = map(int, input().split())
S = input()

result = X
for c in S:
    if c == 'o':
        result += 1
    elif c == 'x':
        if result != 0:
            result -= 1
print(result)

ABC184C - Super Ryuma

突破できず. 頭真っ白になりました.

ABC184D - increment of coins

突破できず. 入出力例でも TLE したり、NaN になったりと上手く行かず.

ABC184E - Third Avenue

CとDを諦めてから始めたので何分かかったのか不明. 56分未満なのは確か. 見るからに難しそうなところが何もなく、何か罠があるのかと思いながら実装したけど、何も罠はなかった(笑). ワープだけは繰り返しトライするとヤバそうだったので、一回しかトライしないようにしたけど、これは見え見えすぎて罠ではないな(笑).

from collections import deque

INF = 10 ** 9

H, W = map(int, input().split())
a = [input() for _ in range(H)]

d = {}
for h in range(H):
    for w in range(W):
        c = a[h][w]
        if c in 'SG':
            d[c] = (h, w)
        elif c in '.#':
            continue
        else:
            if c in d:
                d[c].append((h, w))
            else:
                d[c] = [(h, w)]

not_warped = {}
for c in 'abcdefghijklmnopqrstuvwxyz':
    not_warped[c] = True


def move(h, w, p):
    c = a[h][w]
    if c == '#':
        return
    if t[h][w] > p:
        t[h][w] = p
        q.append((h, w))


t = [[INF] * W for _ in range(H)]
h, w = d['S']
t[h][w] = 0
q = deque([(h, w)])
while q:
    h, w = q.popleft()
    c = a[h][w]
    p = t[h][w] + 1
    if 'a' <= c <= 'z' and not_warped[c]:
        for nh, nw in d[c]:
            if t[nh][nw] > p:
                t[nh][nw] = p
                q.append((nh, nw))
        not_warped[c] = False
    if h != 0:
        move(h - 1, w, p)
    if h != H - 1:
        move(h + 1, w, p)
    if w != 0:
        move(h, w - 1, p)
    if w != W - 1:
        move(h, w + 1, p)

h, w = d['G']
if t[h][w] == INF:
    print(-1)
else:
    print(t[h][w])
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

愛知県の新型コロナ発生事例のPDFをCSVに変換

import datetime
import pathlib
import re
from urllib.parse import urljoin

import pandas as pd
import pdfplumber
import requests
from bs4 import BeautifulSoup


def fetch_file(url, dir="."):

    r = requests.get(url)
    r.raise_for_status()

    p = pathlib.Path(dir, pathlib.PurePath(url).name)
    p.parent.mkdir(parents=True, exist_ok=True)

    with p.open(mode="wb") as fw:
        fw.write(r.content)
    return p


def days2date(s):

    y = dt_now.year

    days = re.findall("[0-9]{1,2}", s)

    if len(days) == 2:
        m, d = map(int, days)
        return pd.Timestamp(year=y, month=m, day=d)
    else:
        return pd.NaT


def wareki2date(s):

    m = re.search("(昭和|平成|令和)([ 0-9元]{1,2})年(\d{1,2})月(\d{1,2})日", s)

    if m:

        year, month, day = [1 if i == "元" else int(i.strip()) for i in m.group(2, 3, 4)]

        if m.group(1) == "昭和":
            year += 1925
        elif m.group(1) == "平成":
            year += 1988
        elif m.group(1) == "令和":
            year += 2018

        return datetime.date(year, month, day)

    else:
        return dt_now.date


url = "https://www.pref.aichi.jp/site/covid19-aichi/"

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"
}

JST = datetime.timezone(datetime.timedelta(hours=+9), "JST")
dt_now = datetime.datetime.now(JST)

r = requests.get(url, headers=headers)
r.raise_for_status()

soup = BeautifulSoup(r.content, "html.parser")

dfs = []
dt_text = ""

for tag in soup.find("span", text="▶ 愛知県内の発生事例").parent.find_all(
    "a", href=re.compile(".pdf$")
)[::-1]:

    link = urljoin(url, tag.get("href"))

    path_pdf = fetch_file(link)

    with pdfplumber.open(path_pdf) as pdf:

        for page in pdf.pages:

            if page.page_number == 1:

                dt_text = page.within_bbox((0, 80, page.width, 90)).extract_text()

            table = page.extract_table()

            df_tmp = pd.DataFrame(table[1:], columns=table[0])

            dfs.append(df_tmp)

df = pd.concat(dfs).set_index("No")

df["発表日"] = df["発表日"].apply(days2date)

df.dropna(subset=["発表日"], inplace=True)

# 年代と性別を分割
df_ages = df["年代・性別"].str.extract("(.+)(男性|女性)").rename(columns={0: "年代", 1: "性別"})

df = df.join(df_ages)


dt_update = wareki2date(dt_text)

path_csv = pathlib.Path(dt_update.strftime("%Y%m%d") + ".csv")

df.to_csv(path_csv, encoding="utf_8_sig")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像処理100本ノック!!(021 - 030)一息入れたい・・・

1. はじめに

 画像の前処理の技術力向上のためにこちらを実践 画像処理100本ノック!!
とっかかりやすいようにColaboratoryでやります。
目標は2週間で完了できるようにやっていきます。丁寧に解説します。質問バシバシください!
001 - 010 は右のリンク  画像処理100本ノック!!(001 - 010)丁寧にじっくりと
011 - 020 は右のリンク  画像処理100本ノック!!(011 - 020)序盤戦

2. 前準備

ライブラリ等々を以下のように導入。

# ライブラリをインポート
from google.colab import drive
import numpy as np
import matplotlib.pyplot as plt
import cv2
from google.colab.patches import cv2_imshow

# 画像の読み込み
img = cv2.imread('画像のパス/imori.jpg')
img_noise = cv2.imread('画像のパス/imori_noise.jpg')
img_dark = cv2.imread('画像のパス/imori_dark.jpg')
img_gamma = cv2.imread('画像のパス/imori_gamma.jpg')
# グレースケール画像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray_noise = cv2.cvtColor(img_noise, cv2.COLOR_BGR2GRAY)
gray_dark = cv2.cvtColor(img_dark, cv2.COLOR_BGR2GRAY)
# 画像保存用
OUT_DIR = '出力先のパス/OUTPUT/'

3.解説

Q.21. ヒストグラム正規化

ヒストグラム正規化を実装せよ。
ヒストグラムは偏りを持っていることが伺える。 例えば、0に近い画素が多ければ画像は全体的に暗く、255に近い画素が多ければ画像は明るくなる。 ヒストグラムが局所的に偏っていることをダイナミックレンジが狭いなどと表現する。 そのため画像を人の目に見やすくするために、ヒストグラムを正規化したり平坦化したりなどの処理が必要である。
このヒストグラム正規化は濃度階調変換(gray-scale transformation) と呼ばれ、[c,d]の画素値を持つ画像を[a,b]のレンジに変換する場合は次式で実現できる。 今回はimori_dark.jpgを[0, 255]のレンジにそれぞれ変換する。
ファイル名

A21
def hist_normalization(img, a=0, b=255):
    """
    ヒストグラム正規化
    params
    ----------------------------
    param1: numpy.ndarray形式のimage
    param2: ヒストグラムレンジの最小値
    param3: ヒストグラムレンジの最大値

    returns
    ----------------------------
    numpy.ndarray形式のimage
    """
    # ヒストグラム(rgb)の最大値・最小値の値
    c = img.min()     # 60
    d = img.max()    # 141

    # コピー
    out = img.copy()

    # 正規化
    out = (b - a) / (d - c) * (out - c) + a
    out[out < a] = a
    out[out > b] = b
    out = out.astype(np.uint8)
    return out

# 画像の高さ、幅、色を取得
H, W, C = img_dark.shape

# ヒストグラム正規化
out = hist_normalization(img_dark)

# ヒストグラムを表示する
plt.hist(out.ravel(), bins=255, rwidth=0.8, range=(0, 255))
plt.savefig("img21.png")
plt.show()

ファイル名
img21_.png
画像もかなり鮮明になった。

参考: ヒストグラム その2: ヒストグラム平坦化

Q.22. ヒストグラム操作

ヒストグラムの平均値をm0=128、標準偏差をs0=52になるように操作せよ。
これはヒストグラムのダイナミックレンジを変更するのではなく、ヒストグラムを平坦に変更する操作である。
平均値m、標準偏差s、のヒストグラムを平均値m0, 標準偏差s0に変更するには、次式によって変換する。
ファイル名

A22
def hist_mani(img, m0=128, s0=52):
    """
    ヒストグラムの平均値をm0=128、標準偏差をs0=52になるように操作

    params
    --------------------------------------
    param1: numpy.ndarray形式のimage
    param2: 平均値
    param3: 標準偏差

    returns
    --------------------------------------
    numpy.ndarray形式のimage
    """
    # 平均値
    m = np.mean(img)
    # 標準偏差
    s = np.std(img)

    # 画像のコピー
    out = img.copy()

    # 計算式通りに計算
    out = s0 / s * (out - m) + m0
    out[out < 0] = 0
    out[out > 255] = 255
    out = out.astype(np.uint8)

    return out

# ヒストグラムを操作
out = hist_mani(img_dark)

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans22_1.jpg', out)
# 画像を表示
cv2_imshow(out)
cv2.waitKey(0)
cv2.destroyAllWindows()

# ヒストグラムを表示する
plt.hist(out.ravel(), bins=255, rwidth=0.8, range=(0, 255))
plt.savefig("img22_2.png")
plt.show()

img22_1.png
ファイル名

参考: ヒストグラム その2: ヒストグラム平坦化

Q.23. ヒストグラム平坦化

ヒストグラム平坦化を実装せよ。
ヒストグラム平坦化とはヒストグラムを平坦に変更する操作であり、上記の平均値や標準偏差などを必要とせず、ヒストグラム値を均衡にする操作である。
これは次式で定義される。 ただし、S ... 画素値の総数、Zmax ... 画素値の最大値、h(z) ... 濃度zの度数
ファイル名

A23
def hist_equal(img, z_max=255):
    """
    ヒストグラム平坦化

    params
    --------------------------------------
    param1: numpy.ndarray形式のimage
    param2:  画素値の最大値

    returns
    --------------------------------------
    numpy.ndarray形式のimage
    """

    # 画像の高さ、幅、色を取得
    H, W, C = img.shape
    # 画素値の総数(画像高さx画像幅x色数)
    S = H * W * C * 1.     # 49152.0
    # 画像のコピー
    out = img.copy()

    # 濃度の度数
    sum_h = 0.

    # 画像の濃度0~255までそれぞれの度数
    for i in range(256):
        # 濃度が一致する箇所
        ind = np.where(img==i)
        # 一致した濃度の度数
        sum_h += len(img[ind])
        # ヒストグラム値を均衡(計算式参照)
        z_prime = z_max / S * sum_h
        out[ind] = z_prime

    out = out.astype(np.uint8)

    return out

# ヒストグラムを操作
out = hist_equal(img)

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans23_1.jpg', out)
# 画像を表示
cv2_imshow(out)
cv2.waitKey(0)
cv2.destroyAllWindows()

# ヒストグラムを表示する
plt.hist(out.ravel(), bins=255, rwidth=0.8, range=(0, 255))
plt.savefig("img23_2.png")
plt.show()

img23_1.png
ファイル名

参考: ヒストグラム その2: ヒストグラム平坦化

Q.24. ガンマ補正

imori_gamma.jpgに対してガンマ補正(c=1, g=2.2)を実行せよ。
ガンマ補正とは、カメラなどの媒体の経由によって画素値が非線形的に変換された場合の補正である。 ディスプレイなどで画像をそのまま表示すると画面が暗くなってしまうため、RGBの値を予め大きくすることで、ディスプレイの特性を排除した画像表示を行うことがガンマ補正の目的である。
非線形変換は次式で起こるとされる。 ただしxは[0,1]に正規化されている。 c ... 定数、g ... ガンマ特性(通常は2.2)
ファイル名
そこで、ガンマ補正は次式で行われる。
ファイル名

A24
def gamma_correction(img, c=1, g=2.2):
    """
    ガンマ補正・・・画像の明るさを調整する方法

    params
    --------------------------------------
    param1: numpy.ndarray形式のimage
    param2: 定数
    param3: ガンマ特性

    returns
    --------------------------------------
    numpy.ndarray形式のimage
    """

    # 画像のコピー
    out = img.copy().astype(np.float)
    # 255で割る(Iinに変換)
    out /= 255.
    # ガンマ補正の式
    out = (1/c * out) ** (1/g)

    # 255をかける
    out *= 255
    out = out.astype(np.uint8)

    return out

# ガンマ補正
out = gamma_correction(img_gamma)

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans24.jpg', out)
# 画像を表示
cv2_imshow(out)
cv2.waitKey(0)
cv2.destroyAllWindows()

img24.png

参考: Pythonやってみる!

Q.25. 最近傍補間

最近傍補間により画像を1.5倍に拡大せよ。
最近傍補間(Nearest Neighbor)は画像の拡大時に最近傍にある画素をそのまま使う手法である。 シンプルで処理速度が速いが、画質の劣化は著しい。
次式で補間される。 I' ... 拡大後の画像、 I ... 拡大前の画像、a ... 拡大率、[ ] ... 四捨五入
ファイル名

A25
"""
最近傍補間
cv2.resize(src, dsize[, interpolation])
src     入力画像
dsize   変更後の画像サイズ
interpolation   補間法(最近傍補間ならcv2.INTER_NEAREST)
"""
# 最近傍補間
# 変更後の画像サイズ: img.shape>>>(高さ、幅、色)
out = cv2.resize(
    img, (int(img.shape[1]*1.5), int(img.shape[0]*1.5)), interpolation=cv2.INTER_NEAREST)

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans25.jpg', out)
# 画像を表示
cv2_imshow(out)
cv2.waitKey(0)
cv2.destroyAllWindows()

img25.png

参考: 【Python/OpenCV】画像の拡大・縮小(最近傍補間法、バイリニア補間法、バイキュービック補間法)

Q.26. Bi-linear補間

Bi-linear補間により画像を1.5倍に拡大せよ。
Bi-linear補間とは周辺の4画素に距離に応じた重みをつけることで補完する手法である。 計算量が多いだけ処理時間がかかるが、画質の劣化を抑えることができる。

A26
"""
バイリニア補間法(Bi-linear interpolation)は、周囲の4つの画素を用いた補間法
cv2.resize(src, dsize[, interpolation])
src     入力画像
dsize   変更後の画像サイズ
interpolation   補間法(バイリニア補間ならcv2.INTER_LINEAR)
"""
# バイリニア補間法
# 変更後の画像サイズ: img.shape>>>(高さ、幅、色)
out = cv2.resize(
    img, (int(img.shape[1]*1.5), int(img.shape[0]*1.5)), interpolation=cv2.INTER_LINEAR)

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans26.jpg', out)
# 画像を表示
cv2_imshow(out)
cv2.waitKey(0)
cv2.destroyAllWindows()

img26.png

参考: 【Python/OpenCV】画像の拡大・縮小(最近傍補間法、バイリニア補間法、バイキュービック補間法)

Q.27. Bi-cubic補間

Bi-cubic補間により画像を1.5倍に拡大せよ。
Bi-cubic補間とはBi-linear補間の拡張であり、周辺の16画素から補間を行う。

A27
"""
バイキュービック補間法では、周囲16画素の画素値を利用
cv2.resize(src, dsize[, interpolation])
src     入力画像
dsize   変更後の画像サイズ
interpolation   補間法(バイキュービック補間ならcv2.INTER_CUBIC)
"""
# バイキュービック補間
# 変更後の画像サイズ: img.shape>>>(高さ、幅、色)
out = cv2.resize(
    img, (int(img.shape[1]*1.5), int(img.shape[0]*1.5)), interpolation=cv2.INTER_CUBIC)

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans27.jpg', out)
# 画像を表示
cv2_imshow(out)
cv2.waitKey(0)
cv2.destroyAllWindows()

img27.png

参考: 【Python/OpenCV】画像の拡大・縮小(最近傍補間法、バイリニア補間法、バイキュービック補間法)

Q.28. アフィン変換(平行移動)

アフィン変換を利用して画像をx方向に+30、y方向に-30だけ平行移動させよ。
アフィン変換とは3x3の行列を用いて画像の変換を行う操作である。

A28
"""
アフィン変換
cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])
第一引数に元画像(NumPy配列ndarray)、
第二引数に2 x 3の変換行列(NumPy配列ndarray)、
第三引数に出力画像のサイズ(タプル)を指定する。
"""
# 画像の高さ、幅、色を取得
H, W, C = img.shape
# 平行移動[[1,0,横方向への移動量],[0,1,縦方向への移動量]]の2x3行列
M = np.float64([[1, 0, 30], [0,1,-30]])
# アフィン変換
out = cv2.warpAffine(img, M, (W, H))

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans28.jpg', out)
# 画像を表示
cv2_imshow(out)
cv2.waitKey(0)
cv2.destroyAllWindows()

img28.png

参考: 【Python/OpenCV】アフィン変換で画像の回転

Q.29. アフィン変換(拡大縮小)

アフィン変換を用いて、(1)x方向に1.3倍、y方向に0.8倍にリサイズせよ。
また、(2) (1)の条件に加えて、x方向に+30、y方向に-30だけ平行移動を同時に実現せよ。

A29
def affine_expand(img, ratio_x, ratio_y):
    """
    アフィン変換で拡大

    params
    -------------------------------
    param1: numpy.ndarray形式のimage
    param2: x方向の比率
    param3: y方向の比率

    returns
    -------------------------------
    numpy.ndarray形式のimage
    """
    # 画像の高さ、幅
    H, W = img.shape[:2]
    # xy座標をnp.float32型
    src = np.array([[0.0, 0.0],[0.0, 1.0],[1.0, 0.0]], np.float32)
    # x, yそれぞれ比率をかける
    dest = src.copy()
    dest[:,0] *= ratio_x
    dest[:,1] *= ratio_y
    """
    アフィン変換の変換行列を生成: cv2.getAffineTransform(src, dest)
    src: 変換前の3点の座標
    dest: 変換後の3点の座標をNumPy配列ndarrayで指定
    """
    affine = cv2.getAffineTransform(src, dest)
    """
    アフィン変換
    cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])
    第一引数に元画像(NumPy配列ndarray)、
    第二引数に2 x 3の変換行列(NumPy配列ndarray)、
    第三引数に出力画像のサイズ(タプル)を指定する。
    INTER_LANCZOS4 – 8×8 の近傍領域を利用するLanczos法での補間
    """
    return cv2.warpAffine(img, affine, (int(W*ratio_x), int(H*ratio_y)), cv2.INTER_LANCZOS4) # 補間法も指定できる


# アフィン変換で拡大
out = affine_expand(img, 1.3, 0.8)
# 平行移動[[1,0,横方向への移動量],[0,1,縦方向への移動量]]の2x3行列
H, W = out.shape[:2]
M = np.float64([[1, 0, 30], [0,1,-30]])
out2 = cv2.warpAffine(out, M, (W, H))

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans29_1.jpg', out)
cv2.imwrite(OUT_DIR + 'ans29_2.jpg', out2)

# 画像を表示
cv2_imshow(out)
cv2_imshow(out2)
cv2.waitKey(0)
cv2.destroyAllWindows()

img29_1.png img29_2.png

参考: 完全に理解するアフィン変換

Q.30. アフィン変換(回転)

(1)アフィン変換を用いて、反時計方向に30度回転させよ。
(2) アフィン変換を用いて、反時計方向に30度回転した画像で中心座標を固定することで、なるべく黒い領域がなくなるように画像を作成せよ。 (ただし、単純なアフィン変換を行うと画像が切れてしまうので、工夫を要する。)

A30
def affin_rotate(img, x, y, theta, scale):
    """
    アフィン変換で回転

    params
    -------------------------------
    param1: numpy.ndarray形式のimage
    param2: 回転軸のx座標
    param3: 回転軸のy座標
    param4: 回転角
    param5: 回転角度・拡大率

    returns
    -------------------------------
    numpy.ndarray形式のimage
    """

    """
    2次元回転を表すアフィン変換
    cv2.getRotationMatrix2D(center, angle, scale)
    center: 回転の原点となる座標
    angle: 回転の角度(ラジアンではなく度degree)
    scale: 拡大・縮小倍率。
    """
    # 回転変換行列の算出
    R = cv2.getRotationMatrix2D((x, y), theta, scale)
    """
    アフィン変換
    cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])
    第一引数に元画像(NumPy配列ndarray)、
    第二引数に2 x 3の変換行列(NumPy配列ndarray)、
    第三引数に出力画像のサイズ(タプル)を指定する。
    cv2.INTER_CUBIC: バイキュービック
    """
    # アフィン変換 
    dst = cv2.warpAffine(img, R, gray.shape,
                        flags=cv2.INTER_CUBIC)

    return dst

# 画像の中心座標
oy, ox = int(img.shape[0]/2), int(img.shape[1]/2)

# 反時計方向に30度回転
out1 = affin_rotate(img, 0, 0, 30, 1)
# 反時計方向に30度回転した画像で中心座標を固定
out2 = affin_rotate(img, ox, oy, 30, 1)

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans30_1.jpg', out1)
cv2.imwrite(OUT_DIR + 'ans31_2.jpg', out2)

# 画像を表示
cv2_imshow(out1)
cv2_imshow(out2)
cv2.waitKey(0)
cv2.destroyAllWindows()

img30_1.png img30_2.png

参考: Python, OpenCVで幾何変換(アフィン変換・射影変換など)
参考: 【Python/OpenCV】アフィン変換で画像の回転

感想

レベルが徐々に上がっているように感じる。可能な限りOpenCVでの実装を心がける。

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

pythonコメントアウト

・行単位のコメントは、#に続けて記述

# Hello, world!を表示
print "Hello, world!"

・複数行にまたがるコメントアウト
'''(シングルクォーテーション3つ)あるいは"""(ダブルクォーテーション3つ)で囲まれた部分が
コメントアウトされる

'''
この行はコメントです。
この行もコメントです。
'''
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】Beautiful SoupでWeb上の画像を一括で保存する方法

はじめに

ここではWebスクレイピングで『Web上の画像を一括で保存する方法』について紹介します。

:warning: 注意 :warning:
著作権で保護されている場合や著作権的にはOKだが利用規約でスクレイピングを禁止している場合、損害賠償請求などの可能性があるのでしっかり著作権法や利用規約を理解した上でWebスクレイピングを行いましょう。

目次

  1. Webスクレイピングを行う方法
  2. 実際に画像を保存してみる
  3. 抽出の流れ
  4. まとめ
  5. おまけ
  6. 参考

1. Webスクレイピングを行う方法

Webスクレイピングを行うためには「Ruby」や「PHP」、「Javascript」など様々な言語で可能ですが、今回はPythonの『Beautiful Soup』を用いた方法を紹介します。

2. 実際に画像を保存してみる

① pipでbeautifulsoup4をインストールする

pip install beautifulsoup4

② Webスクレイピングを行うサイトを決める
※今回は『いらすとや』の画像をダウンロードしていきます。
https://www.irasutoya.com/search/label/%E3%83%93%E3%82%B8%E3%83%8D%E3%82%B9

③ 一覧ページから各画像リンクページURLを取得する

url = "https://www.irasutoya.com/search/label/%E3%83%93%E3%82%B8%E3%83%8D%E3%82%B9"
# 画像ページのURLを格納するリストを用意
link_list = []
response = urllib.request.urlopen(url)
soup = BeautifulSoup(response, "html.parser")
# 画像リンクのタグをすべて取得
image_list = soup.select('div.boxmeta.clearfix > h2 > a')
# 画像リンクを1つずつ取り出す
for image_link in image_list:
    link_url = image_link.attrs['href']
    link_list.append(link_url)

④ 画像ファイルのタグをすべて取得する

for page_url in link_list:
    page_html = urllib.request.urlopen(page_url)
    page_soup = BeautifulSoup(page_html, "html.parser")
    # 画像ファイルのタグをすべて取得
    img_list = page_soup.select('div.separator > a > img')

⑤ imgタグを1つずつ取り出し、画像ファイルのURLを取得する

for img in img_list:
    # 画像ファイルのURLを取得
    img_url = (img.attrs['src'])
    file_name = re.search(".*/(.*png|.*jpg)$", img_url)
    save_path = output_folder.joinpath(file_name.group(1))

⑥ 画像ファイルのURLからデータをダウンロード

try:
   # 画像ファイルのURLからデータを取得
   image = requests.get(img_url)
   # 保存先のファイルパスにデータを保存
   open(save_path, 'wb').write(image.content)
   # 保存したファイル名を表示
   print(save_path)
except ValueError:
   print("ValueError!")

手順としては以上です。

↓ ↓ 実行結果 ↓ ↓
result1.png

3. 抽出の流れ

手順③~⑤が少しイメージしづらいと思ったので、大まかな抽出の流れを作成してみました。
process.png

また、今回のソースはGithubにも掲載してありますので、下記からご参照ください。
https://github.com/miyazakikna/SaveLocalImageWebScraping.git

4. まとめ

ここではPythonのBeatiful Soupを使って画像を一括で保存する方法について解説しました。
今回はいらすとやの画像を取得しましたが、他のサイトでも基本的に同じ要領で画像がダウンロードできるかと思いますので、是非活用してみてください。

5. おまけ

画像ダウンロード後、ファイル名を一括で変更する方法はこちら↓↓
【仕事効率化】Pythonでファイル名を一括変更する方法

6. 参考

Pythonで画像スクレイピングをしよう
webスクレイピングで画像収集

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

小プロセスを含めたkill

概要

小プロセスを含め、killをするためのツールです。python用です。
(pythonのプロセスで縛りを掛けています。)
検証はlinuxのみしてあります。

使い方は、
そのまま実行すると、小プロセスを持つ親プロセスの一覧が表示されるので、
(例としてproc_01.py proc_02.pyの小プロセスを持つプログラムが実行中とします。)

$ python terminate_children_process.py

python 関連の小プロセスを持つprocess一覧
コマンドラインにPIDを指定すると、小プロセスを含めてterminate します。
{'pid': 26727, 'cmdline': ['python', 'proc_01.py']}
{'pid': 26747, 'cmdline': ['python', 'proc_02.py']}

終了させたいpidを指定し再び実行します。

$ python terminate_children_process.py 26747

terminate 子プロセス 26849
terminate 子プロセス 26850
terminate 子プロセス 26851
terminate  親プロセス 26747

ソース(terminate_children_process.py)

#terminate_children_process.py
import sys
import psutil

if len(sys.argv)==1:
    #python 関連の小プロセスを持つprocessの表示
    print("python 関連の小プロセスを持つprocess一覧")
    print("コマンドラインにPIDを指定すると、小プロセスを含めてterminate します。")

    PROCNAME = "python"
    for proc in psutil.process_iter([ "pid"  ,  'cmdline'  ]):
        if proc.name()[:len(PROCNAME)] == PROCNAME:
            p = psutil.Process(proc.pid)
            if len(p.children()) >0:
                print(proc.info)

else:
    #指定したPIDとその小プロセスを含めてterminate

    target_pid=int(sys.argv[1])
    p = psutil.Process(target_pid)

    #子のterminate
    pid_list=[pc.pid for pc in p.children(recursive=True)] 
    for pid in pid_list:
        psutil.Process(pid).terminate ()
        print("terminate 子プロセス {}" .format(pid))

    #親のterminate
    p.terminate ()
    print("terminate  親プロセス {}" .format(target_pid))

参考

psutil documentation

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

Python + Selenium + Chromeでよく使うオプションをメモ

はじめに

Python + Selenium + Chromeでよく使うオプションをメモ

環境情報

  • python: 3.9.0
  • chrome: 86.0.4240.198
  • selenium: 3.141.0

Python + Selenium + Chromeでよく使うオプション

from selenium import webdriver

options = webdriver.chrome.options.Options()
# 画面を非表示
options.add_argument('--headless')
# 必須らしい
options.add_argument('--disable-gpu')
# サイズ指定
options.add_argument('--window-size=1920,1080')
# コンテナの権限不足回避
options.add_argument('--no-sandbox')
# 画像を読み込まない
options.add_argument('--blink-settings=imagesEnabled=false')
# 通知を無効
options.add_argument('--disable-desktop-notifications')
# 拡張機能を無効
options.add_argument('--disable-extensions')
# SSLを無視
options.add_argument('--ignore-certificate-errors')
# Webセキュリティを無効
options.add_argument('--disable-web-security')

driver = webdriver.Chrome(chrome_options=options)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでのアスタリスク(*)の使われ方

概要

Pythonでのアスタリスク(*)の使われ方が、よくわからなかったので調べた。
リファレンスでの説明を確認した。

この記事のポイントは、リファレンスで、どこで説明されているかを示したこと。あと、関数の引数でのアスタリスクは有名だが、引数だけで、極端な使い方の特殊がされているわけでない、ことを示したい( したい)と感じたため。

引用は、すべて、
https://docs.python.org/ja/3.7/reference/

結果

使い方1(乗算演算)

image.png

image.png

image.png

使い方2(べき乗演算)

image.png

使い方3(引数リストのアンパック)

image.png

使い方4(辞書のアンパック)

image.png

image.png

例↓。これは、あまり使う可能性がない。

>>>
>>> ff = {'a':1,'b':2}
>>> gg = {**ff,'c':3}
>>> gg
{'a': 1, 'b': 2, 'c': 3}
>>>

使い方5(イテラブルのアンパック)

image.png

image.png

例↓。これは、使うことがあるかも。

>>>
>>> (hh,*ii)=1,2,3,4,5,6
>>> ii
[2, 3, 4, 5, 6]
>>>

まとめ

特にありません。
コメントなどあれば、お願いします。:candy:

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

Python + Selenium + ChromeでChromeバージョンに合ったChromeDriverを自動インストールする方法

はじめに

selenium + python + chrome でChromeDriverのバージョンエラーに悩まされたので、対処方法をメモ

環境情報

  • python: 3.9.0
  • chrome: 86.0.4240.198
  • selenium: 3.141.0

Chromeバージョンに合ったChromeDriverを自動インストールする方法

下記のように設定すると、Chromeバージョンに合ったChromeDriverが自動インストールされます!

import requests
from selenium import webdriver
from webdriver_manager.utils import chrome_version
from webdriver_manager.chrome import ChromeDriverManager

version = chrome_version()
url = 'http://chromedriver.storage.googleapis.com/LATEST_RELEASE_' + version
response = requests.get(url)

driver = webdriver.Chrome(executable_path=ChromeDriverManager(response.text).install())
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonのSeleniumからChromeでファイルダウンロードを行う方法

はじめに

selenium + python + chrome でファイルダウンロードを行う方法をメモ

環境情報

  • python: 3.9.0
  • chrome: 86.0.4240.198
  • selenium: 3.141.0

ファイルダウンロード設定

下記のように設定すると、【ダウンロード先のパス】にダウンロードされます!

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
driver = webdriver.Chrome(chrome_options=options)

driver.command_executor._commands["send_command"] = (
    "POST",
    '/session/$sessionId/chromium/send_command'
)
params = {
    'cmd': 'Page.setDownloadBehavior',
    'params': {
        'behavior': 'allow',
        'downloadPath': ダウンロード先のパス
    }
}
driver.execute("send_command", params=params)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonでWEBスクレイピングして、レビューからワードクラウドを作ってみる

この記事の内容

MeCabやBeautiful Soupのインポートから、WEBスクレイピングし口コミ、レビューを抽出。それをwordcloudにし、どんな事が書かれているのか可視化してやろう!という内容です。

できるようになること

例えばトリップアドバイザーの「口コミ」これをワードクラウド(頻出単語≒多くの人がわざわざ口コミで言及しているトピック、の可視化)ができます。
東京タワーとスカイツリー、東京タワーと通天閣の可視化などして比較すると差異が見えて面白いかも。。。という発想です。

Tokyo_tower.jpg
赤枠の「口コミ」から、こんなものを作ります。
wordcloud_TT.jpg

参考にさせていただいたサイト、記事

初めてWEBスクレイピングに挑戦し、wordcloudまで作ったのですが、その際に参考にさせていただいたサイトを先に紹介させていただきます。
レビューサイトをスクレイピングして単語数を調査する
【初心者向け】PythonでWebスクレイピングをやってみる
PythonでMeCabを利用する方法を現役エンジニアが解説【初心者向け】
wordcloud を Windows で Anaconda / Jupyter で使う(Tips)

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

まずは必要なライブラリのインストールから行います。(すでに入ってる方は飛ばしてください。)
ちなみに私の環境はWindows 10、Anaconda(jupyter)で行ってます。

Beautiful Soup、request、wordcloud

Ancaonda Promptを起動し、Beautiful Souprequestをインストールします。

conda install beautifulsoup4
conda install request
conda install -c conda-forge wordcloud

MeCab

公式サイトから「Binary package for MS-Windows」をダウンロードし、インストールします。これなら辞書が最初から入っています。慣れてきたら他の辞書に変えても良いでしょう。
インストール途中で文字コードを聞かれますが、「UTF-8」に!他はそのままでOK。
インストールが終わったら、環境変数の設定を行います。
・(おそらくタスクバー左下に合う検索窓で)「システムの詳細」と検索
・「環境変数」を選択
・システム環境変数の「Path」を選択
・編集をクリックし、新規を選択
・「C:\Program Files (x86)\MeCab\bin」と入力
・OKを選択し、画面を閉じる
PythonでMeCabを利用する方法を現役エンジニアが解説【初心者向け】 さんに手順が書いてありますので、そちらもぜひご覧ください。

ここから本番

ここまでで下準備は終わったので、ここからコードを書いていきます。
まずはインポート

import requests
from bs4 import BeautifulSoup
import re
import pandas as pd

ここまでできたら、スクレイピングしたいサイトに行きます。
今回の例ではTrip advisorの東京タワーのページ
以下の2つを確認します。
・URL
・HTMLのどこにスクレイピングしたいもの(今回は口コミ)があるか。

まずURLはそのまま見ればわかるので、説明は省略します。
後者は「F12」を押しデベロッパーツールを起動します。
Untitled.jpg
上記のようなウィンドウが表示されます。
ここから口コミがどこに、どのように格納されているかを確認します。

確認方法は簡単で、「Shift + Ctrl + C」をクリックした後、記事の口コミ部分をCLICKします。
すると、先のウィンドウで該当部分が選択されます。
Untitled2.jpg
口コミはqのclass「IRsGHomP」に格納されていることがわかります。

あとはコード上でこのURL、場所を指定しスクレイピングを実行します。

# スクレイピングした口コミをdf_listに格納していく
df_list = [] 
# 20ページ分スクレイピング行う。
pages = range(0, 100, 5)

for page in pages:
# 1ページ目と2ページ目以降で若干URLが異なるのでIFで分岐
    if page == 0:
        urlName = 'https://www.tripadvisor.jp/Attraction_Review-g14129730-d320047-Reviews-Tokyo_Tower-Shibakoen_Minato_Tokyo_Tokyo_Prefecture_Kanto.html'
    else:
        urlName = 'https://www.tripadvisor.jp/Attraction_Review-g14129730-d320047-Reviews-or' + str(page) + '-Tokyo_Tower-Shibakoen_Minato_Tokyo_Tokyo_Prefecture_Kanto.html'

    url = requests.get(urlName)
    soup = BeautifulSoup(url.content, "html.parser")

# HTMLの中からタグqのClass 'IRsGHoPm'を指定
    review = soup.find_all('q', class_ = 'IRsGHoPm')

# 抜き出した口コミを順番に格納
    for i in range(len(review)):
        _df = pd.DataFrame({'Number':i+1,
                            'review':[review[i].text]})

        df_list.append(_df)

ここまで行うと、df_listには以下が入っているはずです。

df_review = pd.concat(df_list).reset_index(drop=True)
print(df_review.shape)
df_review

Untitled3.jpg

wordcloudの作成

まずはMeCabやWordCloudのインポート

import MeCab
import matplotlib.pyplot as plt
from wordcloud import WordCloud

レビューサイトをスクレイピングして単語数を調査するを参考にさせて頂き、コードを記入。

# MeCab準備
tagger = MeCab.Tagger()
tagger.parse('')

# 全テキストデータを結合
all_text= ""
for s in df_review['review']:
    all_text += s

node = tagger.parseToNode(all_text)

# 名詞を抽出しリストに
word_list = []
while node:
    word_type = node.feature.split(',')[0]
    if word_type == '名詞':
        word_list.append(node.surface)
    node = node.next

# リストを文字列に変換
word_chain = ' '.join(word_list)

あとはwordcloudを実行すればokです。

# ストップワード(除外するワード)の作成
stopwords = ['']

# ワードクラウド作成
W = WordCloud(width=500, height=300, background_color='lightblue', colormap='inferno', font_path='C:\Windows\Fonts\yumin.ttf', stopwords = set(stopwords)).generate(word_chain)

plt.figure(figsize = (15, 12))
plt.imshow(W)
plt.axis('off')
plt.show()

すると下記のように作成されます。
wordcloud_TT.jpg

ただ、「の」や「こと」「ため」とうは不要なので除こうと思います。
そこで、上記のストップワードの出番です。

# ストップワード(除外するワード)の作成
stopwords = ['の', 'こと', 'ため']

# ワードクラウド作成
W = WordCloud(width=500, height=300, background_color='lightblue', colormap='inferno', font_path='C:\Windows\Fonts\yumin.ttf', stopwords = set(stopwords)).generate(word_chain)

plt.figure(figsize = (15, 12))
plt.imshow(W)
plt.axis('off')
plt.show()

すると下記のようになります。
Untitled4.jpg

何かわかるような、何もわからんような。。。
スカイツリーと比較している人が多いのか、「スカイツリーも見えます」という人が多いのか、、とはいえ「スカイツリー」が東京タワー利用者の関心の対象となっていることは間違いなさそうです。
そこでスカイツリーの口コミもワードクラウドにしたものが下記です。

Untitled5.jpg

こちらは「エレベーター」や「チケット」など東京タワーではあまり言及のなかった(文字の大きくなかった)ワードが目立ちます。また「東京タワー」は目立ちません。このあたりは東京タワーとスカイツリーの差異と言えそうです。

おわり。

補足

Open Workなど企業の口コミサイトで、自社と競合を比べてみたりしても面白いかもしれませんし、
札幌ドーム、東京ドーム、名古屋ドーム、大阪ドーム、福岡ドームの五大ドームの口コミ比較もなど、類似施設を比較することで見えてくるものもありそうです。
ちなにみOpen Workのスクレイピングはheaderの設定が必要です。
詳細は下記をご参照ください。
【Python】スクレイピングで403 Forbidden:You don’t have permission to access on this serverが出た際の対処法

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

Twitter Developer + Tweepy でpythonからつぶやく

Twitter Developerの登録はこちらを参照:2度目のTwitter Developer登録で苦労した話
初めての登録なら苦労はしないはず.

アクセストークンを手に入れよう

プロジェクト作成

Twitter Developerに申請済みのアカウントにログイン後,ダッシュボードにアクセス.
まだProjectがなくまっさらの状態なので,+ Create Project からプロジェクトを作成する.
dashbord

この後入力する情報は以下の通り.
1. プロジェクト名(自由入力)
2. 自分の立場(選択式)
3. プロジェクトの説明(自由入力)
4. App名(自由入力)

プロジェクト名とは別にアプリ名も決める必要がある.
例:プロジェクト名は「TestProject」,アプリ名は「MyTestApp」のような命名にするなど

自分の立場は,Twitter Developerに登録する際と同じ選択肢.
今回のプロジェクトに最も見合う立場を選択しよう.
choose

またアプリ名は既に存在するアプリとかぶると使用できない.
error

無事アプリ名が決まると以下の画面に遷移する.
これらの情報は後から確認できるので,まずは App settings から設定に移ろう.
accesstoken

権限の設定

App settingsをクリックすると,作成したプロジェクトのページに遷移する.
ここからTweepyで使用するtokenを手に入れるための設定を行う.
まずはAppsの作成したアプリにある歯車をクリック.
project

アプリの画面に遷移するので,App permissionsEditからアプリの権限を変更する.
apps

TweepyからつぶやくためにはWriteの権限が必要であるため,Read and WriteもしくはRead + Write + Direct Messagesを選択する.セーブも忘れずに.
permission

トークンの取得

次にアプリの画面のKeys and tokensに移動する.
key_and_tokens
API key & secretは,View Keysから何度でも確認できる.
しかしセキュリティの問題で約1年経過すると確認できなくなるため,手元にメモを残すのを忘れずに.
apikey
Access token & secretは1度生成したあと確認し直すことはできないので注意.
メモを取り忘れた/なくした場合,無効化もしくは再生成しかできない.
13.png

Tweepyでpythonからつぶやく

ここからはpythonに移行する.
pip install tweepy でtweepyをインストールしておく.

tweet.py
import tweepy

Consumer_key = '取得したApi key'
Consumer_secret = '取得したAPI Key secret'
Access_token = '取得したAccess token'
Access_secret = '取得したAccess token secret'

# OAuth handler を作成する
auth = tweepy.OAuthHandler(Consumer_key, Consumer_secret)
# OAuth handler にアクセストークンを伝える
auth.set_access_token(Access_token, Access_secret)
# APIを作成する
api = tweepy.API(auth_handler=auth)

# ツイート
api.update_status('APIでツイートしています。')

これだけ!

tokenのベタ書きは危なくない?

もしこのプログラムをGitにあげるとなると,秘密にすべきトークンが全世界に筒抜けになってしまう.
かといってコードが複雑になるとGit管理もしたくなるもの……。

解決策の1つとして,環境変数にトークンを書き込んでしまおう!がある.

Windowsの場合は設定から環境変数の追記が可能.
macの場合,bashを使用しているなら.bashrcに,zshを使用しているなら.zshrcに,以下の4行を書き込む.
(xxxxは各自のkeyに変更.'(クォーテーション)や"(ダブルクォーテーション)は必要ない.)
その後ターミナル上で source ~/.bashrc もしくは source ~/.zshrcで設定を反映させる.

.bashrc/.zshrc
export TWITTER_CK=xxxx
export TWITTER_CS=xxxx
export TWITTER_AT=xxxx
export TWITTER_AS=xxxx

無事環境変数に反映できたら,先程のプログラムを以下のように書き換える.

tweet2.py
import tweepy
import os

Consumer_key = str(os.getenv('TWITTER_CK'))
Consumer_secret = str(os.getenv('TWITTER_CS'))
Access_token = str(os.getenv('TWITTER_AT'))
Access_secret = str(os.getenv('TWITTER_AS'))

# OAuth handler を作成する
auth = tweepy.OAuthHandler(Consumer_key, Consumer_secret)
# OAuth handler にアクセストークンを伝える
auth.set_access_token(Access_token, Access_secret)
# APIを作成する
api = tweepy.API(auth_handler=auth)

# ツイート
api.update_status('APIでツイートしています。')

これで公開しても安全!

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

順列の全探索をするプログラム(競技プログラミング向け)

順列をすべて列挙するプログラムを各言語で書きました。10, 20, 30, 40 の4つの要素があったとして次のような出力をします。要素が $ n $ 個あれば、 $ n! $ 行の出力をします。出力するところになにか処理を書けば順列の全探索ができます。

 10 20 30 40
 10 20 40 30
 10 30 20 40
 10 30 40 20
 10 40 20 30
 10 40 30 20
 20 10 30 40
 20 10 40 30
...

書いた言語:

  • ライブラリ使用: C++, Scala, Ruby, Python
  • 独自実装: C言語, Java, JavaScript, Elixir

経緯:

AtCoderのABC183 C - Travelでは順列の列挙が必要でした。順列列挙のアルゴリズムがわからず、競技中は無理やり書いてACは取れたのですが、解説を読むともっとスマートなアルゴリズムがありました。しかもいくつかの言語では順列を列挙するライブラリも標準装備されていました。

悔しかったので各言語で順列を全部列挙するプログラムを書いたものです。

参考:

C++

C++には next_permutation という関数があります。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
  int len = 4;
  vector<int> vec(len);
  for (int i = 0; i < len; i++) vec[i] = 10 * (i+1);
  do {
    for (int i = 0; i < len; i++) cout << " " << vec[i];
    cout << endl;
  } while (next_permutation(vec.begin(), vec.end()));
}

Scala

Scalaには permutations というメソッドがあります。

object Main extends App {
  val len = 4;
  val seq = (1 to len).map(10 * _);
  seq.permutations.foreach { seq =>
    println(" " + seq.mkString(" "));
  }
}

Ruby

Rubyには permutation というメソッドがあります。

len = 4
arr = (1 .. len).map do |i|
  10 * i
end
arr.permutation do |arr|
  puts(" " + arr.join(" "))
end

Python

Pythonには itertools.permutations という関数があります。

import itertools

len = 4
lst = [10 * (i+1) for i in range(len)]
for lst2 in itertools.permutations(lst):
    print(" " + " ".join(map(str, lst2)))

C言語

C++の next_permutation を自分で実装しています。(ほとんどAtCoderの解説そのままです)

#include <stdio.h>

int next_permutation(int *arr, int len) {
  int left = len - 2;
  while (left >= 0 && arr[left] >= arr[left+1]) left--;
  if (left < 0) return 0;
  int right = len - 1;
  while (arr[left] >= arr[right]) right--;
  { int t = arr[left]; arr[left] = arr[right]; arr[right] = t; }
  left++;
  right = len - 1;
  while (left < right) {
    { int t = arr[left]; arr[left] = arr[right]; arr[right] = t; }
    left++;
    right--;
  }
  return 1;
}

int main() {
  int len = 4;
  int arr[len];
  for (int i = 0; i < len; i++) arr[i] = 10 * (i+1);
  do {
    for (int i = 0; i < len; i++) printf(" %d", arr[i]);
    printf("\n");
  } while (next_permutation(arr, len));
}

Java

C言語で実装した next_permutation と同じ実装です。

class Main {
  public static boolean nextPermutation(int[] arr) {
    int len = arr.length;
    int left = len - 2;
    while (left >= 0 && arr[left] >= arr[left+1]) left--;
    if (left < 0) return false;
    int right = len - 1;
    while (arr[left] >= arr[right]) right--;
    { int t = arr[left]; arr[left] = arr[right];  arr[right] = t; }
    left++;
    right = len - 1;
    while (left < right) {
      { int t = arr[left]; arr[left] = arr[right]; arr[right] = t; }
      left++;
      right--;
    }
    return true;
  }

  public static void main(String[] args) {
    int len = 4;
    var arr = new int[len];
    for (int i = 0; i < len; i++) {
      arr[i] = 10 * (i+1);
    }
    do {
      var sb = new StringBuilder();
      for (int i = 0; i < len; i++) {
        sb.append(String.format(" %d", arr[i]));
      }
      System.out.println(sb);
    } while (nextPermutation(arr));
  }
}

JavaScript

C言語やJavaで実装した next_permutation と同じ実装です。

function next_permutation(arr) {
    const len = arr.length;
    let left = len - 2;
    while (left >= 0 && arr[left] >= arr[left+1]) left--;
    if (left < 0) return false;
    let right = len - 1;
    while (arr[left] >= arr[right]) right--;
    { const t = arr[left]; arr[left] = arr[right]; arr[right] = t; }
    left++;
    right = len - 1;
    while (left < right) {
        { const t = arr[left]; arr[left] = arr[right]; arr[right] = t; }
        left++;
        right--;
    }
    return true;
}

const len = 4;
const arr = [];
for (let i = 0; i < len; i++) arr.push(10 * (i+1));
do {
    let str = "";
    for (let i = 0; i < len; i++) str += " " + arr[i];
    console.log(str);
} while (next_permutation(arr));

Elixir

C言語やJavaでの実装とは異なるアルゴリズムです。 next_permutation と違って、順列の全パターンを始めに作成しています。

defmodule Main do
  def permutations([]), do: [[]]
  def permutations(list) do
    for elem <- list, p <- permutations(list -- [elem]), do: [elem | p]
  end

  def main do
    len = 4
    list = (1 .. len) |> Enum.map(fn x -> 10 * x end)
    permutations(list) |> Enum.each(fn l ->
      IO.puts(" " <> Enum.join(l, " "))
    end)
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Virus TotalのAPIを利用してハッシュ値から検体のレポートを取得する方法

はじめに

手元に検体のハッシュ値情報はあるが,多すぎて手作業で調査するのは大変なので,自動的にVirus Total からレポート情報を取得するプログラムを作成しました.プログラムはPython3 で作成および動作確認しています.
同じような記事が多数ありますが,Python2系で記述されていたり,公式ドキュメントのサンプルと微妙に異なる書き方してあったりするので,記事を作成することにしました.

Virus Total は参考文献にある通り,API を提供しているので,情報の取得にはAPIを使用するのが効率的です.
Virus Total のAPI key は,アカウントを作成することで無料で入手できます.
第三者に漏洩すると悪用される恐れがあるので,プログラムにハードコーディングする際は公開や共有時に注意ください.
(自分しか使わないならハードコーディングしても良いかと思いますが,外部公開を考えると外部入力したほうが無難かもしれません)

プログラム

公式ドキュメントに沿って,以下の内容で作成しました.
apikey の部分はあなた自身のapi key の値を記述してください.(前述した通り外部入力の形でも結構です.)

また,本プログラムは検体のハッシュ値情報をほかのテキストファイルから受け取る形で実装しています.こちらもハードコーディングするか,実行時の引数で取得するかはやりやすい方法で実装してください.

get_vt.py
import sys
import json
import time
import requests

url = 'https://www.virustotal.com/vtapi/v2/file/report'

count = 0
file = open('hash.txt', 'r')
for hash in file:
    params = {'apikey': 'your api key value', 'resource': hash}
    response = requests.get(url, params=params)
    print(response.json())
    count = count + 1
    if count % 4 == 0:
        time.sleep(65)
file.close()

よく聞く話なのですが,API に断続的にアクセスするとアクセスに制限が適用されてしまうことがあるらしく,Virus Total だと4回続いた後は60秒空ける必要があるという噂を聞いたので,4回に1回は念を含めて65秒動作をストップするようにしています.

実行方法

ただのPythonスクリプトなので,以下の方法で実行します.結果はJSON フォーマットで返ってくるので,jsonファイルに出力させます.

get_vt.pyの実行方法
$ python3 get_vt.py > vt_result.json

以下は,ハッシュ値 a5a0420200af84fdb5674569f1a8eafe7ef7b41b で検索した場合の取得結果です.アンチウイルス製品名に続いて,マルウェア判定の結果がdetected にTrue or False で記述されるみたいです.マルウェア名も取得できるようで,見る限りはMirai っぽいです.

取得結果
{'scans': {'Bkav': {'detected': False, 'version': '1.3.0.9899', 'result': None, 'update': '20200819'}, 'MicroWorld-eScan': {'detected': False, 'version': '14.0.409.0', 'result': None, 'update': '20200820'}, 'FireEye': {'detected': True, 'version': '32.36.1.0', 'result': 'Trojan.Linux.Mirai.1', 'update': '20200820'}, 'CAT-QuickHeal': {'detected': False, 'version': '14.00', 'result': None, 'update': '20200820'}, 'McAfee': {'detected': True, 'version': '6.0.6.653', 'result': 'RDN/Generic BackDoor', 'update': '20200820'}, 'Malwarebytes': {'detected': False, 'version': '3.6.4.335', 'result': None, 'update': '20200820'}, 'Zillya': {'detected': True, 'version': '2.0.0.4158', 'result': 'Backdoor.Mirai.Linux.91998', 'update': '20200820'}, 'SUPERAntiSpyware': {'detected': False, 'version': '5.6.0.1032', 'result': None, 'update': '20200814'}, 'Sangfor': {'detected': False, 'version': '1.0', 'result': None, 'update': '20200814'}, 'K7AntiVirus': {'detected': False, 'version': '11.131.35049', 'result': None, 'update': '20200820'}, 'K7GW': {'detected': False, 'version': '11.131.35050', 'result': None, 'update': '20200820'}, 'Baidu': {'detected': False, 'version': '1.0.0.2', 'result': None, 'update': '20190318'}, 'F-Prot': {'detected': False, 'version': '4.7.1.166', 'result': None, 'update': '20200820'}, 'Symantec': {'detected': True, 'version': '1.11.0.0', 'result': 'Trojan.Gen.NPE', 'update': '20200820'}, 'ESET-NOD32': {'detected': True, 'version': '21852', 'result': 'a variant of Linux/Mirai.OX', 'update': '20200820'}, 'TrendMicro-HouseCall': {'detected': False, 'version': '10.0.0.1040', 'result': None, 'update': '20200820'}, 'Avast': {'detected': True, 'version': '18.4.3895.0', 'result': 'Other:Malware-gen [Trj]', 'update': '20200820'}, 'ClamAV': {'detected': True, 'version': '0.102.4.0', 'result': 'Unix.Dropper.Mirai-7135870-0', 'update': '20200817'}, 'Kaspersky': {'detected': True, 'version': '15.0.1.13', 'result': 'HEUR:Backdoor.Linux.Mirai.b', 'update': '20200820'}, 'BitDefender': {'detected': True, 'version': '7.2', 'result': 'Trojan.Linux.Mirai.1', 'update': '20200820'}, 'NANO-Antivirus': {'detected': True, 'version': '1.0.134.25140', 'result': 'Trojan.Mirai.hrbzkk', 'update': '20200820'}, 'ViRobot': {'detected': False, 'version': '2014.3.20.0', 'result': None, 'update': '20200820'}, 'Tencent': {'detected': True, 'version': '1.0.0.1', 'result': 'Backdoor.Linux.Mirai.wao', 'update': '20200820'}, 'Ad-Aware': {'detected': False, 'version': '3.0.16.117', 'result': None, 'update': '20200820'}, 'TACHYON': {'detected': False, 'version': '2020-08-20.02', 'result': None, 'update': '20200820'}, 'Comodo': {'detected': True, 'version': '32668', 'result': '.UnclassifiedMalware@0', 'update': '20200728'}, 'F-Secure': {'detected': True, 'version': '12.0.86.52', 'result': 'Malware.LINUX/Mirai.lpnjw', 'update': '20200820'}, 'DrWeb': {'detected': True, 'version': '7.0.46.3050', 'result': 'Linux.Mirai.671', 'update': '20200820'}, 'VIPRE': {'detected': False, 'version': '86068', 'result': None, 'update': '20200820'}, 'TrendMicro': {'detected': True, 'version': '11.0.0.1006', 'result': 'Backdoor.Linux.MIRAI.USELVH120', 'update': '20200820'}, 'CMC': {'detected': False, 'version': '2.7.2019.1', 'result': None, 'update': '20200820'}, 'Sophos': {'detected': True, 'version': '4.98.0', 'result': 'Linux/DDoS-CIA', 'update': '20200819'}, 'Cyren': {'detected': False, 'version': '6.3.0.2', 'result': None, 'update': '20200820'}, 'Jiangmin': {'detected': False, 'version': '16.0.100', 'result': None, 'update': '20200820'}, 'Avira': {'detected': True, 'version': '8.3.3.8', 'result': 'LINUX/Mirai.lpnjw', 'update': '20200820'}, 'Fortinet': {'detected': True, 'version': '6.2.142.0', 'result': 'ELF/DDoS.CIA!tr', 'update': '20200820'}, 'Antiy-AVL': {'detected': False, 'version': '3.0.0.1', 'result': None, 'update': '20200820'}, 'Kingsoft': {'detected': False, 'version': '2013.8.14.323', 'result': None, 'update': '20200820'}, 'Arcabit': {'detected': True, 'version': '1.0.0.877', 'result': 'Trojan.Linux.Mirai.1', 'update': '20200820'}, 'AegisLab': {'detected': True, 'version': '4.2', 'result': 'Trojan.Linux.Mirai.K!c', 'update': '20200820'}, 'AhnLab-V3': {'detected': False, 'version': '3.18.1.10026', 'result': None, 'update': '20200820'}, 'ZoneAlarm': {'detected': True, 'version': '1.0', 'result': 'HEUR:Backdoor.Linux.Mirai.b', 'update': '20200820'}, 'Avast-Mobile': {'detected': False, 'version': '200820-00', 'result': None, 'update': '20200820'}, 'Microsoft': {'detected': True, 'version': '1.1.17300.4', 'result': 'Trojan:Win32/Skeeyah.A!rfn', 'update': '20200820'}, 'Cynet': {'detected': True, 'version': '4.0.0.24', 'result': 'Malicious (score: 85)', 'update': '20200815'}, 'TotalDefense': {'detected': False, 'version': '37.1.62.1', 'result': None, 'update': '20200820'}, 'BitDefenderTheta': {'detected': False, 'version': '7.2.37796.0', 'result': None, 'update': '20200819'}, 'ALYac': {'detected': False, 'version': '1.1.1.5', 'result': None, 'update': '20200820'}, 'MAX': {'detected': True, 'version': '2019.9.16.1', 'result': 'malware (ai score=89)', 'update': '20200820'}, 'VBA32': {'detected': False, 'version': '4.4.1', 'result': None, 'update': '20200819'}, 'Zoner': {'detected': False, 'version': '0.0.0.0', 'result': None, 'update': '20200819'}, 'Rising': {'detected': True, 'version': '25.0.0.26', 'result': 'Backdoor.Mirai/Linux!1.BAF6 (CLASSIC)', 'update': '20200820'}, 'Yandex': {'detected': False, 'version': '5.5.2.24', 'result': None, 'update': '20200707'}, 'Ikarus': {'detected': True, 'version': '0.1.5.2', 'result': 'Trojan.Linux.Mirai', 'update': '20200820'}, 'MaxSecure': {'detected': False, 'version': '1.0.0.1', 'result': None, 'update': '20200819'}, 'GData': {'detected': True, 'version': 'A:25.26670B:27.19869', 'result': 'Trojan.Linux.Mirai.1', 'update': '20200820'}, 'AVG': {'detected': True, 'version': '18.4.3895.0', 'result': 'Other:Malware-gen [Trj]', 'update': '20200820'}, 'Panda': {'detected': False, 'version': '4.6.4.2', 'result': None, 'update': '20200819'}, 'Qihoo-360': {'detected': True, 'version': '1.0.0.1120', 'result': 'Linux/Backdoor.6f4', 'update': '20200820'}}, 'scan_id': '0aa5949d00c05b62cb5e9ac24f11b08cd5ed13f089b628220d6cc27b5147230c-1597909074', 'sha1': 'a5a0420200af84fdb5674569f1a8eafe7ef7b41b', 'resource': '0aa5949d00c05b62cb5e9ac24f11b08cd5ed13f089b628220d6cc27b5147230c', 'response_code': 1, 'scan_date': '2020-08-20 07:37:54', 'permalink': 'https://www.virustotal.com/gui/file/0aa5949d00c05b62cb5e9ac24f11b08cd5ed13f089b628220d6cc27b5147230c/detection/f-0aa5949d00c05b62cb5e9ac24f11b08cd5ed13f089b628220d6cc27b5147230c-1597909074', 'verbose_msg': 'Scan finished, information embedded', 'total': 59, 'positives': 29, 'sha256': '0aa5949d00c05b62cb5e9ac24f11b08cd5ed13f089b628220d6cc27b5147230c', 'md5': '1e0621f530a9f1cb000d670c54a789c9'}

まとめ

Virus Total のAPI を利用してハッシュ値からレポート情報を取得するプログラムを作成した.今後は,得られた出力情報の利用方法やほかのAPI の利用方法などについて検討していく.

参考文献

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

3. Pythonによる自然言語処理 3-1. 重要語抽出ツール TF-IDF分析[原定義]

  • 自然言語処理を行うとき、具体的な狙いの一つとして「ある文章を特徴づけるような重要語を抽出したい」ということがあります。
  • 単語を抽出するとき、まずはテキスト内で出現回数の多い単語を拾います。出現頻度順のリストの上位に挙がってくるのは、あらゆる文章に共通して頻繁に使われる語ばかりです。
  • 品詞情報を使って名詞に限定しても、例えば「事」や「時」などのように特定の意味をなさない汎用的な単語が上位に多数出てくるので、それらをストップワードとして除外するなどの処理が必要です。

⑴ TF-IDFという考え方

  • TF-IDF(Term Frequency - Inverse Document Frequency)、直訳すると「用語頻度 - 逆文書頻度」です。
  • 出現回数は多いが、その語が出てくる文書の数が少ない、つまりどこにでも出てくるわけではない単語を特徴的で重要な語であると判定する考え方です。
  • 多くは単語を対象とするものですが、文字や句(フレーズ)にも適用できますし、文書(ドキュメント)の単位もいろいろ応用がききます。

⑵ TF-IDF値の定義

出現頻度 $tf$ に、希少性の指標となる係数 $idf$ を掛け算する

  • $tfidf=tf×idf$
  • $tf_{ij}$ (単語$i$の文書$j$における出現頻度) × $idf_{i}$(単語$i$を含む文書の数の逆数の$log$)

出現頻度 $tf$ と係数 $idf$ はそれぞれ次のように定義されます

  • $tf_{ij}=\dfrac{n_{ij}}{\sum_{k}n_{kj}} = \dfrac{単語iの文書jにおける出現回数}{文書jにおける全ての単語の出現回数の和}$
  • $idf_{i}=\log \dfrac{|D|}{|{d:d∋t_{i}}|} = \log{\dfrac{全文書の数}{単語iを含む文書の数}}$

⑶ 原定義による計算のしくみ

# 数値計算ライブラリのインポート
from math import log
import pandas as pd
import numpy as np

➀ 単語データリストを用意

  • すでに形態素解析などの前処理を済ませて、6つの文書について次のような単語のリストができているという想定で、対象となる3つの単語の$tfidf$を計算します。
docs = [
        ["語1", "語3", "語1", "語3", "語1"],
        ["語1", "語1"],
        ["語1", "語1", "語1"],
        ["語1", "語1", "語1", "語1"],
        ["語1", "語1", "語2", "語2", "語1"],
        ["語1", "語3", "語1", "語1"]
        ]

N = len(docs)

words = list(set(w for doc in docs for w in doc))
words.sort()

print("文書数:", N)
print("対象となる単語:", words)

image.png

➁ 計算用の関数を定義

  • あらかじめ出現頻度 $tf$、係数 $idf$、それらを掛け合わせた $tfidf$ をそれぞれ計算する関数を定義します。
# 関数tfの定義
def tf(t, d):
    return d.count(t)/len(d)

# 関数idfの定義
def idf(t):
    df = 0
    for doc in docs:
        df += t in doc
    return np.log10(N/df)

# 関数tfidfの定義
def tfidf(t, d):
    return tf(t,d) * idf(t)

➂ TFの計算結果を観察

  • 参考までに、段階的に $tf$ 及び $idf$ の計算結果をみておきましょう。
# tfを計算
result = []
for i in range(N):
    temp = []
    d = docs[i]
    for j in range(len(words)):
        t = words[j]     
        temp.append(tf(t,d))
    result.append(temp)

pd.DataFrame(result, columns=words)

image.png

➃ IDFの計算結果を観察

# idfを計算
result = []
for j in range(len(words)):
    t = words[j]
    result.append(idf(t))

pd.DataFrame(result, index=words, columns=["IDF"])

image.png

  • 6つの文書すべてに出てくる語1の係数 $idf$ は 0 となり、また1つの文書にしか出てこない語2は最も大きく0.778151 となっています。

➄ TF-IDFの計算

# tfidfを計算
result = []
for i in range(N):
    temp = []
    d = docs[i]
    for j in range(len(words)):
        t = words[j]
        temp.append(tfidf(t,d))   
    result.append(temp)

pd.DataFrame(result, columns=words)

image.png

  • 語1の $tfidf$ は、係数 $idf$ が 0 なので 、たとえ出現回数がどんなに多くても一律 0 になってしまいます。
  • また、TF-IDFはそもそも情報検索の目的で提案された指標であり、1回も出現しない語に対しては $idf$ を計算する上で分母が 0(いわゆるゼロ除算)となってエラーになってしまいます。

⑷ scikit-learnによる計算

  • そうした問題点に対応して scikit-learn の TF-IDF ライブラリTfidfVectorizerは、原定義とはやや異なる定義で実装されています。
# scikit-learnのTF-IDFライブラリをインポート
from sklearn.feature_extraction.text import TfidfVectorizer
  • 先の6つの文書の単語データリストを対象にTfidfVectorizerをつかって $tfidf$ を計算してみます。
# 1次元のリスト
docs = [
        "語1 語3 語1 語3 語1",
        "語1 語1",
        "語1 語1 語1",
        "語1 語1 語1 語1",
        "語1 語1 語2 語2 語1",
        "語1 語3 語1 語1"
        ]

# モデルを生成
vectorizer = TfidfVectorizer(smooth_idf=False)
X = vectorizer.fit_transform(docs)

# データフレームに表現
values = X.toarray()
feature_names = vectorizer.get_feature_names()
pd.DataFrame(values,
             columns = feature_names)

image.png

  • 語1の $tfidf$ は各文書で 0 ではなくなり、また語2、語3においては元から 0 であった文書を除いて原定義とは異なる値になっています。
  • そこで、原定義に基づいて scikit-learn の結果を再現してみます。

⑸ scikit-learnの結果を再現

➀ IDFの計算式を変更

# 関数idfの定義
def idf(t):
    df = 0
    for doc in docs:
        df += t in doc
    #return np.log10(N/df)
    return np.log(N/df)+1
  • np.log10(N/df)np.log(N/df)+1に修正
  • つまり底を10とする常用対数から、底をネイピア数eとする自然対数に変えて、さらに+1します。
# idfを計算
result = []
for j in range(len(words)):
    t = words[j]
    result.append(idf(t))

pd.DataFrame(result, index=words, columns=["IDF"])

image.png

➁ TF-IDFの計算結果を観察

# tfidfを計算
result = []
for i in range(N):
    temp = []
    d = docs[i]
    for j in range(len(words)):
        t = words[j]
        temp.append(tfidf(t,d))   
    result.append(temp)

pd.DataFrame(result, columns=words)

image.png

➂ TF-IDF計算結果をL2正則化

  • 最後に $tfidf$ の計算結果をL2正則化します。
  • つまり値をスケーリングして、それらがすべて2乗されて合計すると1になるように変換します。
  • なぜ正則化が必要かといえば、各単語が各文書に出現する回数を数えているわけですが、各文書は長さが異なりますので、文書が長いほど単語の数は多くなりがちです。
  • そうした単語の合計数による影響を取り除くことによって、単語の出現頻度を相対的に比較することが可能となります。
# 試しに文書1のみ定義に従ってノルム値を計算
x = np.array([0.60, 0.000000, 0.839445])
x_norm = sum(x**2)**0.5
x_norm = x/x_norm
print(x_norm)

# それらを2乗して合計し、1になることを確認
np.sum(x_norm**2)

image.png

  • ここは scikit-learn を利用して楽をしましょう。
# scikit-learnの正則化ライブラリをインポート
from sklearn.preprocessing import normalize

# L2正則化
result_norm = normalize(result, norm='l2')

# データフレームに表現
pd.DataFrame(result_norm, columns=words)

image.png

  • 整理しておくと、scikit-learn の TF-IDF は、原定義のもつ2つの難点を解消するものとなっています。
  • 係数 $idf$ の計算式に自然対数を用い、かつ+1することによってゼロを回避する。ちなみに常用対数から自然対数への変換は約 2.303 倍になります。
  • さらにL2正則化によって、文書ごとの長短に伴う単語数の差の影響が排除されています。
  • TF-IDF の原理はとてもシンプルなものですが、scikit-learn にはパラメータもいろいろありますし、課題によってチューニングしていく余地もありそうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Qiskit: VQEを実装してみた

QiskitでVQEを実装してみた.

今回はIBMが提供している量子コンピュータ用のオープンソースフレームワークを用いてVQE(Variational Quantum Eigensolver)を実装していきます.このアルゴリズムは量子化学への応用が期待されているアルゴリズムです.理論的な解説はQuantum Native Dojoを参照お願いします.
VQEの実装記事は複数あるとは思いますが,日本語記事の殆どはQulacsやBlueqatを用いて書かれているので今回Qiskitで実装を試みてみました.過去にQAOAやQCLの記事も書いているので是非みてみてください.
Qiskit Aquaを使わないQAOAの実装
Quantum Circuit Learningの実装

実装

ライブラリのimport

from qiskit import Aer, execute
from qiskit import QuantumCircuit
from qiskit.aqua.utils import tensorproduct
from qiskit.quantum_info.analysis import average_data

from scipy.optimize import minimize
import numpy as np

初期化

class VQE:

    def __init__(self, n_qubits, observable, layer, backend):
        self.n_qubits = n_qubits
        self.OBS = observable
        self.Layer = layer
        self.backend = backend

今回はHamiltonianも入力として扱います.

回路の作成

    def make_circuit(self, Params):

        def make_U_circuit(circ, params):
            for n in range(self.n_qubits):
                param = params[3*n:3*(n+1)]
                circ.rz(param[0], n)
                circ.rx(param[1], n)
                circ.rz(param[2], n)
            return circ

        def make_Ent(circ):
            for n in range(self.n_qubits-1):
                circ.cx(n, n+1)
            return circ

        # 回路を設定
        circ = QuantumCircuit(self.n_qubits, self.n_qubits)

        for l in range(self.Layer):
            # parameter circuitの作成
            params = Params[3*self.n_qubits*l:3*self.n_qubits*(l+1)]
            # entanglementの作成
            make_U_circuit(circ, params)
            # Layerの数-1にしなければならないので
            if l != self.Layer-1:
                make_Ent(circ)

        # 測定
        circ.measure(circ.qregs[0], circ.cregs[0])

        return circ

VQE実行フェーズ

    def outputlayer(self, params):
        circ = self.make_circuit(params)
        counts = execute(circ, backend=self.backend, shots=8192).result().get_counts()
        return average_data(counts, self.OBS)

    def initial_params(self):
        # 初期parameterの作成
        init = [0.1 for _ in range(3 * self.Layer * self.n_qubits)]
        return np.array(init)

    def minimize(self):
        initial_params = self.initial_params()
        # 最適化の実行
        opt_params, opt_cost = classica_minimize(self.outputlayer, initial_params, options={'maxiter':500})
        circ = self.make_circuit(opt_params)
        counts = execute(circ, backend=self.backend, shots=8192).result().get_counts()
        ans = sorted(counts.items(), key=lambda x: x[1], reverse=True)
        print(ans)
        return opt_cost

なお,他の記事と異なりlayerというのを使用しています.これは参考文献[1]に載っているものです.軽く解説させていただきます.
$\theta=(\theta_1, \theta_2, \cdots, \theta_d)$をパラメータの集合とした時,回路は

U(\theta) = U_d(\theta_d)U_{ENT} \cdots U_1(\theta_1)U_{ENT}U_0(\theta_0)

と書けます.この時の$d$がlayer数となります.$d=2$とすることで他の記事と同じ回路を作成することが可能となります.

実行

今回はHamiltonianとして他の記事でも扱っている式を用います.
Supplementary Information

def sample_hamiltonian():
    '''
    https://dojo.qulacs.org/ja/latest/notebooks/5.1_variational_quantum_eigensolver.html
    のHamiltonianを使用.
    '''
    I_mat = np.array([[1, 0], [0, 1]])
    X_mat = np.array([[0, 1], [1, 0]])
    Z_mat = np.array([[1, 0], [0, -1]])
    obs = np.zeros((4, 4))
    obs += -3.8505 * tensorproduct(I_mat, I_mat)
    obs += -0.2288 * tensorproduct(I_mat, X_mat)
    obs += -1.0466 * tensorproduct(I_mat, Z_mat)
    obs += 0.2613 * tensorproduct(X_mat, X_mat)
    obs += 0.2288 * tensorproduct(X_mat, Z_mat)
    obs += -1.0466 * tensorproduct(Z_mat, I_mat)
    obs += 0.2288 * tensorproduct(Z_mat, X_mat)
    obs += 0.2356 * tensorproduct(Z_mat, Z_mat)
    return obs / 2

また,古典最適化として私が書いている他の記事でも用いている関数を使います.

def classica_minimize(cost_func, initial_params, options, method='powell'):
    print('classical minimize is starting now... ')
    result = minimize(cost_func, initial_params, options=options, method=method)
    print('opt_cost: {}'.format(result.fun))
    print('opt_params: {}'.format(result.x))
    return result.x, result.fun

実行です.

if __name__ == '__main__':
    backend = Aer.get_backend('qasm_simulator')
    obs = sample_hamiltonian()
    vqe = VQE(2, obs, 2, backend)
    vqe.minimize()

実行結果

-2.85405 # vqeの結果
-2.8626207640766816 # 最小固有値

他の記事と比較すると精度は落ちますけど一応成功ですかね...?

まとめ

Qiskitを用いてVQEの実装を行ってみました.
正直私自身はVQEよりも古典的問題に適しているQAOAの方が好きなので完全に実装を目的として記事を書かせていただきました.
理論等に関してはご自身でお願いします...

参考文献

[1] Hardware-efficient variational quantum eigensolver for small molecules and quantum magnets
[2] Quantum Native Dojo

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

〔備忘録〕プログラミング事始め

はじめに

現在私は自然科学系の調査を生業としています。そこまで解析が困難な膨大なデータを扱うわけではなく、データ解析にはエクセルがあればほぼ十分。少し難しい解析はRがあればほぼすべてをカバーできるような状況です。ただ、最近機械学習だとか、電子工作だとか、業務の自動化だとか目にすると先々なにか役に立つのではないかということでプログラミングは未経験だけどなにか初めて見ようかなと思った次第です。

勉強したいこと

1.python
機械学習に特化しており、自分の業務になんらかの関連性を見出したからです。
2.電子工作
ArduinoとかESP8266とかラズパイとか。先端技術とは程遠いわが研究分野になんらかの光をさしてくれそうだから。

心掛けること

今までは本ばっかり読んでなんらアウトプットはしてきませんでした。なるべく勉強結果をそのまま記載するでもいいからアウトプットを心掛けるようにしたいです。アウトプットしないとまったく覚えていられないんです。いつまで続くかはわかりませんが頑張りたいです。

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

Webセーフカラーコード一覧

Webセーフカラー カラーコード一覧

Webセーフカラーについて勉強したので、カラーコード一覧をつくってみました。

カラーコード (R, G, B) 補色 反対色
#000000 (0, 0, 0) #000000 #ffffff
#000033 (0, 0, 51) #333300 #ffffcc
#000066 (0, 0, 102) #666600 #ffff99
#000099 (0, 0, 153) #999900 #ffff66
#0000cc (0, 0, 204) #cccc00 #ffff33
#0000ff (0, 0, 255) #ffff00 #ffff00
#003300 (0, 51, 0) #330033 #ffccff
#003333 (0, 51, 51) #330000 #ffcccc
#003366 (0, 51, 102) #663300 #ffcc99
#003399 (0, 51, 153) #996600 #ffcc66
#0033cc (0, 51, 204) #cc9900 #ffcc33
#0033ff (0, 51, 255) #ffcc00 #ffcc00
#006600 (0, 102, 0) #660066 #ff99ff
#006633 (0, 102, 51) #660033 #ff99cc
#006666 (0, 102, 102) #660000 #ff9999
#006699 (0, 102, 153) #993300 #ff9966
#0066cc (0, 102, 204) #cc6600 #ff9933
#0066ff (0, 102, 255) #ff9900 #ff9900
#009900 (0, 153, 0) #990099 #ff66ff
#009933 (0, 153, 51) #990066 #ff66cc
#009966 (0, 153, 102) #990033 #ff6699
#009999 (0, 153, 153) #990000 #ff6666
#0099cc (0, 153, 204) #cc3300 #ff6633
#0099ff (0, 153, 255) #ff6600 #ff6600
#00cc00 (0, 204, 0) #cc00cc #ff33ff
#00cc33 (0, 204, 51) #cc0099 #ff33cc
#00cc66 (0, 204, 102) #cc0066 #ff3399
#00cc99 (0, 204, 153) #cc0033 #ff3366
#00cccc (0, 204, 204) #cc0000 #ff3333
#00ccff (0, 204, 255) #ff3300 #ff3300
#00ff00 (0, 255, 0) #ff00ff #ff00ff
#00ff33 (0, 255, 51) #ff00cc #ff00cc
#00ff66 (0, 255, 102) #ff0099 #ff0099
#00ff99 (0, 255, 153) #ff0066 #ff0066
#00ffcc (0, 255, 204) #ff0033 #ff0033
#00ffff (0, 255, 255) #ff0000 #ff0000
#330000 (51, 0, 0) #003333 #ccffff
#330033 (51, 0, 51) #003300 #ccffcc
#330066 (51, 0, 102) #336600 #ccff99
#330099 (51, 0, 153) #669900 #ccff66
#3300cc (51, 0, 204) #99cc00 #ccff33
#3300ff (51, 0, 255) #ccff00 #ccff00
#333300 (51, 51, 0) #000033 #ccccff
#333333 (51, 51, 51) #333333 #cccccc
#333366 (51, 51, 102) #666633 #cccc99
#333399 (51, 51, 153) #999933 #cccc66
#3333cc (51, 51, 204) #cccc33 #cccc33
#3333ff (51, 51, 255) #ffff33 #cccc00
#336600 (51, 102, 0) #330066 #cc99ff
#336633 (51, 102, 51) #663366 #cc99cc
#336666 (51, 102, 102) #663333 #cc9999
#336699 (51, 102, 153) #996633 #cc9966
#3366cc (51, 102, 204) #cc9933 #cc9933
#3366ff (51, 102, 255) #ffcc33 #cc9900
#339900 (51, 153, 0) #660099 #cc66ff
#339933 (51, 153, 51) #993399 #cc66cc
#339966 (51, 153, 102) #993366 #cc6699
#339999 (51, 153, 153) #993333 #cc6666
#3399cc (51, 153, 204) #cc6633 #cc6633
#3399ff (51, 153, 255) #ff9933 #cc6600
#33cc00 (51, 204, 0) #9900cc #cc33ff
#33cc33 (51, 204, 51) #cc33cc #cc33cc
#33cc66 (51, 204, 102) #cc3399 #cc3399
#33cc99 (51, 204, 153) #cc3366 #cc3366
#33cccc (51, 204, 204) #cc3333 #cc3333
#33ccff (51, 204, 255) #ff6633 #cc3300
#33ff00 (51, 255, 0) #cc00ff #cc00ff
#33ff33 (51, 255, 51) #ff33ff #cc00cc
#33ff66 (51, 255, 102) #ff33cc #cc0099
#33ff99 (51, 255, 153) #ff3399 #cc0066
#33ffcc (51, 255, 204) #ff3366 #cc0033
#33ffff (51, 255, 255) #ff3333 #cc0000
#660000 (102, 0, 0) #006666 #99ffff
#660033 (102, 0, 51) #006633 #99ffcc
#660066 (102, 0, 102) #006600 #99ff99
#660099 (102, 0, 153) #339900 #99ff66
#6600cc (102, 0, 204) #66cc00 #99ff33
#6600ff (102, 0, 255) #99ff00 #99ff00
#663300 (102, 51, 0) #003366 #99ccff
#663333 (102, 51, 51) #336666 #99cccc
#663366 (102, 51, 102) #336633 #99cc99
#663399 (102, 51, 153) #669933 #99cc66
#6633cc (102, 51, 204) #99cc33 #99cc33
#6633ff (102, 51, 255) #ccff33 #99cc00
#666600 (102, 102, 0) #000066 #9999ff
#666633 (102, 102, 51) #333366 #9999cc
#666666 (102, 102, 102) #666666 #999999
#666699 (102, 102, 153) #999966 #999966
#6666cc (102, 102, 204) #cccc66 #999933
#6666ff (102, 102, 255) #ffff66 #999900
#669900 (102, 153, 0) #330099 #9966ff
#669933 (102, 153, 51) #663399 #9966cc
#669966 (102, 153, 102) #996699 #996699
#669999 (102, 153, 153) #996666 #996666
#6699cc (102, 153, 204) #cc9966 #996633
#6699ff (102, 153, 255) #ffcc66 #996600
#66cc00 (102, 204, 0) #6600cc #9933ff
#66cc33 (102, 204, 51) #9933cc #9933cc
#66cc66 (102, 204, 102) #cc66cc #993399
#66cc99 (102, 204, 153) #cc6699 #993366
#66cccc (102, 204, 204) #cc6666 #993333
#66ccff (102, 204, 255) #ff9966 #993300
#66ff00 (102, 255, 0) #9900ff #9900ff
#66ff33 (102, 255, 51) #cc33ff #9900cc
#66ff66 (102, 255, 102) #ff66ff #990099
#66ff99 (102, 255, 153) #ff66cc #990066
#66ffcc (102, 255, 204) #ff6699 #990033
#66ffff (102, 255, 255) #ff6666 #990000
#990000 (153, 0, 0) #009999 #66ffff
#990033 (153, 0, 51) #009966 #66ffcc
#990066 (153, 0, 102) #009933 #66ff99
#990099 (153, 0, 153) #009900 #66ff66
#9900cc (153, 0, 204) #33cc00 #66ff33
#9900ff (153, 0, 255) #66ff00 #66ff00
#993300 (153, 51, 0) #006699 #66ccff
#993333 (153, 51, 51) #339999 #66cccc
#993366 (153, 51, 102) #339966 #66cc99
#993399 (153, 51, 153) #339933 #66cc66
#9933cc (153, 51, 204) #66cc33 #66cc33
#9933ff (153, 51, 255) #99ff33 #66cc00
#996600 (153, 102, 0) #003399 #6699ff
#996633 (153, 102, 51) #336699 #6699cc
#996666 (153, 102, 102) #669999 #669999
#996699 (153, 102, 153) #669966 #669966
#9966cc (153, 102, 204) #99cc66 #669933
#9966ff (153, 102, 255) #ccff66 #669900
#999900 (153, 153, 0) #000099 #6666ff
#999933 (153, 153, 51) #333399 #6666cc
#999966 (153, 153, 102) #666699 #666699
#999999 (153, 153, 153) #999999 #666666
#9999cc (153, 153, 204) #cccc99 #666633
#9999ff (153, 153, 255) #ffff99 #666600
#99cc00 (153, 204, 0) #3300cc #6633ff
#99cc33 (153, 204, 51) #6633cc #6633cc
#99cc66 (153, 204, 102) #9966cc #663399
#99cc99 (153, 204, 153) #cc99cc #663366
#99cccc (153, 204, 204) #cc9999 #663333
#99ccff (153, 204, 255) #ffcc99 #663300
#99ff00 (153, 255, 0) #6600ff #6600ff
#99ff33 (153, 255, 51) #9933ff #6600cc
#99ff66 (153, 255, 102) #cc66ff #660099
#99ff99 (153, 255, 153) #ff99ff #660066
#99ffcc (153, 255, 204) #ff99cc #660033
#99ffff (153, 255, 255) #ff9999 #660000
#cc0000 (204, 0, 0) #00cccc #33ffff
#cc0033 (204, 0, 51) #00cc99 #33ffcc
#cc0066 (204, 0, 102) #00cc66 #33ff99
#cc0099 (204, 0, 153) #00cc33 #33ff66
#cc00cc (204, 0, 204) #00cc00 #33ff33
#cc00ff (204, 0, 255) #33ff00 #33ff00
#cc3300 (204, 51, 0) #0099cc #33ccff
#cc3333 (204, 51, 51) #33cccc #33cccc
#cc3366 (204, 51, 102) #33cc99 #33cc99
#cc3399 (204, 51, 153) #33cc66 #33cc66
#cc33cc (204, 51, 204) #33cc33 #33cc33
#cc33ff (204, 51, 255) #66ff33 #33cc00
#cc6600 (204, 102, 0) #0066cc #3399ff
#cc6633 (204, 102, 51) #3399cc #3399cc
#cc6666 (204, 102, 102) #66cccc #339999
#cc6699 (204, 102, 153) #66cc99 #339966
#cc66cc (204, 102, 204) #66cc66 #339933
#cc66ff (204, 102, 255) #99ff66 #339900
#cc9900 (204, 153, 0) #0033cc #3366ff
#cc9933 (204, 153, 51) #3366cc #3366cc
#cc9966 (204, 153, 102) #6699cc #336699
#cc9999 (204, 153, 153) #99cccc #336666
#cc99cc (204, 153, 204) #99cc99 #336633
#cc99ff (204, 153, 255) #ccff99 #336600
#cccc00 (204, 204, 0) #0000cc #3333ff
#cccc33 (204, 204, 51) #3333cc #3333cc
#cccc66 (204, 204, 102) #6666cc #333399
#cccc99 (204, 204, 153) #9999cc #333366
#cccccc (204, 204, 204) #cccccc #333333
#ccccff (204, 204, 255) #ffffcc #333300
#ccff00 (204, 255, 0) #3300ff #3300ff
#ccff33 (204, 255, 51) #6633ff #3300cc
#ccff66 (204, 255, 102) #9966ff #330099
#ccff99 (204, 255, 153) #cc99ff #330066
#ccffcc (204, 255, 204) #ffccff #330033
#ccffff (204, 255, 255) #ffcccc #330000
#ff0000 (255, 0, 0) #00ffff #00ffff
#ff0033 (255, 0, 51) #00ffcc #00ffcc
#ff0066 (255, 0, 102) #00ff99 #00ff99
#ff0099 (255, 0, 153) #00ff66 #00ff66
#ff00cc (255, 0, 204) #00ff33 #00ff33
#ff00ff (255, 0, 255) #00ff00 #00ff00
#ff3300 (255, 51, 0) #00ccff #00ccff
#ff3333 (255, 51, 51) #33ffff #00cccc
#ff3366 (255, 51, 102) #33ffcc #00cc99
#ff3399 (255, 51, 153) #33ff99 #00cc66
#ff33cc (255, 51, 204) #33ff66 #00cc33
#ff33ff (255, 51, 255) #33ff33 #00cc00
#ff6600 (255, 102, 0) #0099ff #0099ff
#ff6633 (255, 102, 51) #33ccff #0099cc
#ff6666 (255, 102, 102) #66ffff #009999
#ff6699 (255, 102, 153) #66ffcc #009966
#ff66cc (255, 102, 204) #66ff99 #009933
#ff66ff (255, 102, 255) #66ff66 #009900
#ff9900 (255, 153, 0) #0066ff #0066ff
#ff9933 (255, 153, 51) #3399ff #0066cc
#ff9966 (255, 153, 102) #66ccff #006699
#ff9999 (255, 153, 153) #99ffff #006666
#ff99cc (255, 153, 204) #99ffcc #006633
#ff99ff (255, 153, 255) #99ff99 #006600
#ffcc00 (255, 204, 0) #0033ff #0033ff
#ffcc33 (255, 204, 51) #3366ff #0033cc
#ffcc66 (255, 204, 102) #6699ff #003399
#ffcc99 (255, 204, 153) #99ccff #003366
#ffcccc (255, 204, 204) #ccffff #003333
#ffccff (255, 204, 255) #ccffcc #003300
#ffff00 (255, 255, 0) #0000ff #0000ff
#ffff33 (255, 255, 51) #3333ff #0000cc
#ffff66 (255, 255, 102) #6666ff #000099
#ffff99 (255, 255, 153) #9999ff #000066
#ffffcc (255, 255, 204) #ccccff #000033
#ffffff (255, 255, 255) #ffffff #000000

Webセーフカラー

Webセーフカラーとは、ブラウザやOSといった環境が異なっても同じように表示されるカラーのこと。
RGBをそれぞれ6段階(16進数表記の場合、00、33、66、99、CC、FF)に分け、それらの組み合わせでできる色。
全216色。

Pythonコード

上記を出力したPythonコードは以下です。
環境は、Google Colaboratoryを使用しました。

print('|カラーコード|(R, G, B)|補色|反対色|')
print('|:---:|:---:|:---:|:---:|')
for r in range(0, 256, 51):
  for g in range(0, 256, 51):
    for b in range(0, 256, 51):      
      # 補色の計算
      tmp = max(r, g, b) + min(r, g, b)
      r_c = tmp - r
      g_c = tmp - g
      b_c = tmp - b
      # 反対色の計算
      r_o = 255 - r
      g_o = 255 - g
      b_o = 255 - b
      color_code = '#' + format(r, '02x') + format(g, '02x') + format(b, '02x') # 16進数に変換
      rgb = '({}, {}, {})'.format(r, g, b)
      color_code_comp = '#' + format(r_c, '02x') + format(g_c, '02x') + format(b_c, '02x')
      color_code_opposit = '#' + format(r_o, '02x') + format(g_o, '02x') + format(b_o, '02x')
      print('|`{}`|`{}`|`{}`|`{}`|'.format(color_code, rgb, color_code_comp, color_code_opposit))

補色

補色は、RGB値の最大値と最小値を足した値から、RGBそれぞれの値を引いたものになります。

反対色

反対色は、255からRGBそれぞれの値を引いたものになります。

参考

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

Webセーフカラーのカラーコード一覧

Webセーフカラー カラーコード一覧

Webセーフカラーについて勉強したので、カラーコード一覧をつくってみました。

カラーコード (R, G, B) 補色 反対色
#000000 (0, 0, 0) #000000 #ffffff
#000033 (0, 0, 51) #333300 #ffffcc
#000066 (0, 0, 102) #666600 #ffff99
#000099 (0, 0, 153) #999900 #ffff66
#0000cc (0, 0, 204) #cccc00 #ffff33
#0000ff (0, 0, 255) #ffff00 #ffff00
#003300 (0, 51, 0) #330033 #ffccff
#003333 (0, 51, 51) #330000 #ffcccc
#003366 (0, 51, 102) #663300 #ffcc99
#003399 (0, 51, 153) #996600 #ffcc66
#0033cc (0, 51, 204) #cc9900 #ffcc33
#0033ff (0, 51, 255) #ffcc00 #ffcc00
#006600 (0, 102, 0) #660066 #ff99ff
#006633 (0, 102, 51) #660033 #ff99cc
#006666 (0, 102, 102) #660000 #ff9999
#006699 (0, 102, 153) #993300 #ff9966
#0066cc (0, 102, 204) #cc6600 #ff9933
#0066ff (0, 102, 255) #ff9900 #ff9900
#009900 (0, 153, 0) #990099 #ff66ff
#009933 (0, 153, 51) #990066 #ff66cc
#009966 (0, 153, 102) #990033 #ff6699
#009999 (0, 153, 153) #990000 #ff6666
#0099cc (0, 153, 204) #cc3300 #ff6633
#0099ff (0, 153, 255) #ff6600 #ff6600
#00cc00 (0, 204, 0) #cc00cc #ff33ff
#00cc33 (0, 204, 51) #cc0099 #ff33cc
#00cc66 (0, 204, 102) #cc0066 #ff3399
#00cc99 (0, 204, 153) #cc0033 #ff3366
#00cccc (0, 204, 204) #cc0000 #ff3333
#00ccff (0, 204, 255) #ff3300 #ff3300
#00ff00 (0, 255, 0) #ff00ff #ff00ff
#00ff33 (0, 255, 51) #ff00cc #ff00cc
#00ff66 (0, 255, 102) #ff0099 #ff0099
#00ff99 (0, 255, 153) #ff0066 #ff0066
#00ffcc (0, 255, 204) #ff0033 #ff0033
#00ffff (0, 255, 255) #ff0000 #ff0000
#330000 (51, 0, 0) #003333 #ccffff
#330033 (51, 0, 51) #003300 #ccffcc
#330066 (51, 0, 102) #336600 #ccff99
#330099 (51, 0, 153) #669900 #ccff66
#3300cc (51, 0, 204) #99cc00 #ccff33
#3300ff (51, 0, 255) #ccff00 #ccff00
#333300 (51, 51, 0) #000033 #ccccff
#333333 (51, 51, 51) #333333 #cccccc
#333366 (51, 51, 102) #666633 #cccc99
#333399 (51, 51, 153) #999933 #cccc66
#3333cc (51, 51, 204) #cccc33 #cccc33
#3333ff (51, 51, 255) #ffff33 #cccc00
#336600 (51, 102, 0) #330066 #cc99ff
#336633 (51, 102, 51) #663366 #cc99cc
#336666 (51, 102, 102) #663333 #cc9999
#336699 (51, 102, 153) #996633 #cc9966
#3366cc (51, 102, 204) #cc9933 #cc9933
#3366ff (51, 102, 255) #ffcc33 #cc9900
#339900 (51, 153, 0) #660099 #cc66ff
#339933 (51, 153, 51) #993399 #cc66cc
#339966 (51, 153, 102) #993366 #cc6699
#339999 (51, 153, 153) #993333 #cc6666
#3399cc (51, 153, 204) #cc6633 #cc6633
#3399ff (51, 153, 255) #ff9933 #cc6600
#33cc00 (51, 204, 0) #9900cc #cc33ff
#33cc33 (51, 204, 51) #cc33cc #cc33cc
#33cc66 (51, 204, 102) #cc3399 #cc3399
#33cc99 (51, 204, 153) #cc3366 #cc3366
#33cccc (51, 204, 204) #cc3333 #cc3333
#33ccff (51, 204, 255) #ff6633 #cc3300
#33ff00 (51, 255, 0) #cc00ff #cc00ff
#33ff33 (51, 255, 51) #ff33ff #cc00cc
#33ff66 (51, 255, 102) #ff33cc #cc0099
#33ff99 (51, 255, 153) #ff3399 #cc0066
#33ffcc (51, 255, 204) #ff3366 #cc0033
#33ffff (51, 255, 255) #ff3333 #cc0000
#660000 (102, 0, 0) #006666 #99ffff
#660033 (102, 0, 51) #006633 #99ffcc
#660066 (102, 0, 102) #006600 #99ff99
#660099 (102, 0, 153) #339900 #99ff66
#6600cc (102, 0, 204) #66cc00 #99ff33
#6600ff (102, 0, 255) #99ff00 #99ff00
#663300 (102, 51, 0) #003366 #99ccff
#663333 (102, 51, 51) #336666 #99cccc
#663366 (102, 51, 102) #336633 #99cc99
#663399 (102, 51, 153) #669933 #99cc66
#6633cc (102, 51, 204) #99cc33 #99cc33
#6633ff (102, 51, 255) #ccff33 #99cc00
#666600 (102, 102, 0) #000066 #9999ff
#666633 (102, 102, 51) #333366 #9999cc
#666666 (102, 102, 102) #666666 #999999
#666699 (102, 102, 153) #999966 #999966
#6666cc (102, 102, 204) #cccc66 #999933
#6666ff (102, 102, 255) #ffff66 #999900
#669900 (102, 153, 0) #330099 #9966ff
#669933 (102, 153, 51) #663399 #9966cc
#669966 (102, 153, 102) #996699 #996699
#669999 (102, 153, 153) #996666 #996666
#6699cc (102, 153, 204) #cc9966 #996633
#6699ff (102, 153, 255) #ffcc66 #996600
#66cc00 (102, 204, 0) #6600cc #9933ff
#66cc33 (102, 204, 51) #9933cc #9933cc
#66cc66 (102, 204, 102) #cc66cc #993399
#66cc99 (102, 204, 153) #cc6699 #993366
#66cccc (102, 204, 204) #cc6666 #993333
#66ccff (102, 204, 255) #ff9966 #993300
#66ff00 (102, 255, 0) #9900ff #9900ff
#66ff33 (102, 255, 51) #cc33ff #9900cc
#66ff66 (102, 255, 102) #ff66ff #990099
#66ff99 (102, 255, 153) #ff66cc #990066
#66ffcc (102, 255, 204) #ff6699 #990033
#66ffff (102, 255, 255) #ff6666 #990000
#990000 (153, 0, 0) #009999 #66ffff
#990033 (153, 0, 51) #009966 #66ffcc
#990066 (153, 0, 102) #009933 #66ff99
#990099 (153, 0, 153) #009900 #66ff66
#9900cc (153, 0, 204) #33cc00 #66ff33
#9900ff (153, 0, 255) #66ff00 #66ff00
#993300 (153, 51, 0) #006699 #66ccff
#993333 (153, 51, 51) #339999 #66cccc
#993366 (153, 51, 102) #339966 #66cc99
#993399 (153, 51, 153) #339933 #66cc66
#9933cc (153, 51, 204) #66cc33 #66cc33
#9933ff (153, 51, 255) #99ff33 #66cc00
#996600 (153, 102, 0) #003399 #6699ff
#996633 (153, 102, 51) #336699 #6699cc
#996666 (153, 102, 102) #669999 #669999
#996699 (153, 102, 153) #669966 #669966
#9966cc (153, 102, 204) #99cc66 #669933
#9966ff (153, 102, 255) #ccff66 #669900
#999900 (153, 153, 0) #000099 #6666ff
#999933 (153, 153, 51) #333399 #6666cc
#999966 (153, 153, 102) #666699 #666699
#999999 (153, 153, 153) #999999 #666666
#9999cc (153, 153, 204) #cccc99 #666633
#9999ff (153, 153, 255) #ffff99 #666600
#99cc00 (153, 204, 0) #3300cc #6633ff
#99cc33 (153, 204, 51) #6633cc #6633cc
#99cc66 (153, 204, 102) #9966cc #663399
#99cc99 (153, 204, 153) #cc99cc #663366
#99cccc (153, 204, 204) #cc9999 #663333
#99ccff (153, 204, 255) #ffcc99 #663300
#99ff00 (153, 255, 0) #6600ff #6600ff
#99ff33 (153, 255, 51) #9933ff #6600cc
#99ff66 (153, 255, 102) #cc66ff #660099
#99ff99 (153, 255, 153) #ff99ff #660066
#99ffcc (153, 255, 204) #ff99cc #660033
#99ffff (153, 255, 255) #ff9999 #660000
#cc0000 (204, 0, 0) #00cccc #33ffff
#cc0033 (204, 0, 51) #00cc99 #33ffcc
#cc0066 (204, 0, 102) #00cc66 #33ff99
#cc0099 (204, 0, 153) #00cc33 #33ff66
#cc00cc (204, 0, 204) #00cc00 #33ff33
#cc00ff (204, 0, 255) #33ff00 #33ff00
#cc3300 (204, 51, 0) #0099cc #33ccff
#cc3333 (204, 51, 51) #33cccc #33cccc
#cc3366 (204, 51, 102) #33cc99 #33cc99
#cc3399 (204, 51, 153) #33cc66 #33cc66
#cc33cc (204, 51, 204) #33cc33 #33cc33
#cc33ff (204, 51, 255) #66ff33 #33cc00
#cc6600 (204, 102, 0) #0066cc #3399ff
#cc6633 (204, 102, 51) #3399cc #3399cc
#cc6666 (204, 102, 102) #66cccc #339999
#cc6699 (204, 102, 153) #66cc99 #339966
#cc66cc (204, 102, 204) #66cc66 #339933
#cc66ff (204, 102, 255) #99ff66 #339900
#cc9900 (204, 153, 0) #0033cc #3366ff
#cc9933 (204, 153, 51) #3366cc #3366cc
#cc9966 (204, 153, 102) #6699cc #336699
#cc9999 (204, 153, 153) #99cccc #336666
#cc99cc (204, 153, 204) #99cc99 #336633
#cc99ff (204, 153, 255) #ccff99 #336600
#cccc00 (204, 204, 0) #0000cc #3333ff
#cccc33 (204, 204, 51) #3333cc #3333cc
#cccc66 (204, 204, 102) #6666cc #333399
#cccc99 (204, 204, 153) #9999cc #333366
#cccccc (204, 204, 204) #cccccc #333333
#ccccff (204, 204, 255) #ffffcc #333300
#ccff00 (204, 255, 0) #3300ff #3300ff
#ccff33 (204, 255, 51) #6633ff #3300cc
#ccff66 (204, 255, 102) #9966ff #330099
#ccff99 (204, 255, 153) #cc99ff #330066
#ccffcc (204, 255, 204) #ffccff #330033
#ccffff (204, 255, 255) #ffcccc #330000
#ff0000 (255, 0, 0) #00ffff #00ffff
#ff0033 (255, 0, 51) #00ffcc #00ffcc
#ff0066 (255, 0, 102) #00ff99 #00ff99
#ff0099 (255, 0, 153) #00ff66 #00ff66
#ff00cc (255, 0, 204) #00ff33 #00ff33
#ff00ff (255, 0, 255) #00ff00 #00ff00
#ff3300 (255, 51, 0) #00ccff #00ccff
#ff3333 (255, 51, 51) #33ffff #00cccc
#ff3366 (255, 51, 102) #33ffcc #00cc99
#ff3399 (255, 51, 153) #33ff99 #00cc66
#ff33cc (255, 51, 204) #33ff66 #00cc33
#ff33ff (255, 51, 255) #33ff33 #00cc00
#ff6600 (255, 102, 0) #0099ff #0099ff
#ff6633 (255, 102, 51) #33ccff #0099cc
#ff6666 (255, 102, 102) #66ffff #009999
#ff6699 (255, 102, 153) #66ffcc #009966
#ff66cc (255, 102, 204) #66ff99 #009933
#ff66ff (255, 102, 255) #66ff66 #009900
#ff9900 (255, 153, 0) #0066ff #0066ff
#ff9933 (255, 153, 51) #3399ff #0066cc
#ff9966 (255, 153, 102) #66ccff #006699
#ff9999 (255, 153, 153) #99ffff #006666
#ff99cc (255, 153, 204) #99ffcc #006633
#ff99ff (255, 153, 255) #99ff99 #006600
#ffcc00 (255, 204, 0) #0033ff #0033ff
#ffcc33 (255, 204, 51) #3366ff #0033cc
#ffcc66 (255, 204, 102) #6699ff #003399
#ffcc99 (255, 204, 153) #99ccff #003366
#ffcccc (255, 204, 204) #ccffff #003333
#ffccff (255, 204, 255) #ccffcc #003300
#ffff00 (255, 255, 0) #0000ff #0000ff
#ffff33 (255, 255, 51) #3333ff #0000cc
#ffff66 (255, 255, 102) #6666ff #000099
#ffff99 (255, 255, 153) #9999ff #000066
#ffffcc (255, 255, 204) #ccccff #000033
#ffffff (255, 255, 255) #ffffff #000000

Webセーフカラー

Webセーフカラーとは、ブラウザやOSといった環境が異なっても同じように表示されるカラーのこと。
RGBをそれぞれ6段階(16進数表記の場合、00、33、66、99、CC、FF)に分け、それらの組み合わせでできる色。
全216色。

Pythonコード

上記を出力したPythonコードは以下です。
環境は、Google Colaboratoryを使用しました。

print('|カラーコード|(R, G, B)|補色|反対色|')
print('|:---:|:---:|:---:|:---:|')
for r in range(0, 256, 51):
  for g in range(0, 256, 51):
    for b in range(0, 256, 51):      
      # 補色の計算
      tmp = max(r, g, b) + min(r, g, b)
      r_c = tmp - r
      g_c = tmp - g
      b_c = tmp - b
      # 反対色の計算
      r_o = 255 - r
      g_o = 255 - g
      b_o = 255 - b
      color_code = '#' + format(r, '02x') + format(g, '02x') + format(b, '02x') # 16進数に変換
      rgb = '({}, {}, {})'.format(r, g, b)
      color_code_comp = '#' + format(r_c, '02x') + format(g_c, '02x') + format(b_c, '02x')
      color_code_opposit = '#' + format(r_o, '02x') + format(g_o, '02x') + format(b_o, '02x')
      print('|`{}`|`{}`|`{}`|`{}`|'.format(color_code, rgb, color_code_comp, color_code_opposit))

補色

補色は、RGB値の最大値と最小値を足した値から、RGBそれぞれの値を引いたものになります。

反対色

反対色は、255からRGBそれぞれの値を引いたものになります。

参考

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

深層強化学習3 実践編:ブロック崩し

Aidemy 2020/11/22

はじめに

 こんにちは、んがょぺです!バリバリの文系ですが、AIの可能性に興味を持ったのがきっかけで、AI特化型スクール「Aidemy」に通い、勉強しています。ここで得られた知識を皆さんと共有したいと思い、Qiitaでまとめています。以前のまとめ記事も多くの方に読んでいただけてとても嬉しいです。ありがとうございます!
 今回は、深層強化学習の3つ目の投稿になります。どうぞよろしくお願いします。

*本記事は「Aidemy」での学習内容を「自分の言葉で」まとめたものになります。表現の間違いや勘違いを含む可能性があります。ご了承ください。

今回学ぶこと

ブロック崩しによる強化学習実践

環境の作成

・Chapter2と同じ方法(gym.make())で環境を作成する。ブロック崩しの場合、引数には「BreakoutDeterministic-v4」を指定する。
・行動数は「env.action_space.n」で確認できる。

・コード
スクリーンショット 2020-11-20 14.52.49.png

モデルの構築

・ここでは多層ニューラルネットワークを構築する。入力は「ブロック崩しの画面4フレーム分」とする。また、計算量を少なくするために、画像はグレースケールの84×84画素にリサイズする。
・モデルはSequential()を使用する。Chapter2と同様にFlatten()で入力を平滑化し、全結合層はDense、活性化関数はActivationで追加する。
・今回は画像(二次元)の入力なので、二次元の畳み込み層である「Convolution2D()」を使用する。第一引数は「filter」で、出力空間の次元数を指定し、第二引数は「kernel_size」で、畳み込んだウィンドウの幅と高さを指定する。「strides」は歩幅、つまりウィンドウの一度に動く幅と高さを指定する。

・コード
スクリーンショット 2020-11-20 15.21.21.png

履歴と方策の設定

・ここもChapter2と同様に、エージェントの作成に必要な履歴方策を設定する。
・履歴は「SequentialMemory()」を使う。引数にはlimitwindow_lengthを指定する。
・方策は、ボルツマン方策をとる場合は「BoltzmannQPolicy()」、ε-greedy手法をとる場合は「EpsGreedyQPolicy()」を使う。
・また、パラメータεを線形に変化させるときは、「LinearAnnealedPolicy()」を使用する。引数は、以下のコードのように指定すると、トレーニングの際にパラメータεを10ステップで最大1.0最小0.1の線形に変形し、テストの時は0.05で固定するという意味を表す。

・コード
スクリーンショット 2020-11-20 15.59.01.png

エージェントの設定

・エージェントは「DQNAgent()」の引数にmodel,memory,policy,nb_actions,nb_steps_warmupを渡すことで作成できる。あとは「dqn.compile()」で学習方法を指定すれば良い。第一引数には最適化アルゴリズムを指定し、第二引数には評価関数を指定する。

・コードスクリーンショット 2020-11-20 16.08.36.png

学習の実施

・前項の設定まで終わったら、次にDQNアルゴリズムを使って学習を行う。「dqn.fit()」で行い、第一引数には環境、第二引数には「nb_steps」で何ステップ学習するかを指定する。
・また、学習結果は「dqn.save_weights()」でhdf5の形式で保存できる。第一引数にはファイル名、第二引数には「overwrite」で上書き可能にするかを指定する。

・コード
スクリーンショット 2020-11-21 11.15.16.png

テストの実施

・学習させたエージェントでテストを行う。「dqn.test()」で行う。引数はfitと同じで、ステップ数nb_stepsの代わりにエピソード数「nb_episodes」を指定する。
・ちなみに、今回のブロック崩しでは、ボールを落とすまでが1エピソードである。

Dueling DQN

Dueling DQNとは

Dueling DQN(DDQN)はDQNの発展版で、DQNのネットワーク層の最後を変更したものである。
・DQNでは、最初に「convolution層」が3つの後に、全結合層を経てQ値を出力していたが、DDQNはこの全結合層を2つにわけ、一方では状態価値Vを出力し、もう一方では行動Aを出力する。この2つを入力とした最後の全結合層からQ値を求めることで、DQNよりも性能が上がる。

・図スクリーンショット 2020-11-21 11.32.40.png

Dueling DQNの実装

・Dueling DQNの実装は、層の追加まではDQNと同じである。エージェントの設定時(DQNAgent())に、引数で「enable_dueling_network=True」とし、Q値の求め方「dueling_type」を指定することで実装できる。dueling_typeには「'avg','max','naive'」を指定できる。

・コードスクリーンショット 2020-11-21 12.12.37.png

・結果スクリーンショット 2020-11-21 12.13.16.png

まとめ

・ブロック崩しでも、Chapter2の時のように環境などを定義できる。
・モデルの構築については、今回は二次元の画像認識であるので、畳み込みを使用する。「Convolution2D」層を使用する。
・今回の方策ではε-greedy手法を使うのだが、パラメータεは線形に変化させる必要がある。このような時は、「LinearAnnealedPolicy()」を使って線形に変化させる。
・学習まで行ったモデルは「dqn.save_weights()」を使うことでhdf5の形式で保存できる。
DuelingDQN全結合を2つに分け、それぞれ状態価値Vと行動Aを算出し、最後の層で、その二つからQ値を求めるDQNである。実装はDQNAgent()「enable_dueling_network」「dueling_type」を指定すれば良い。

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

Pythonでthisを使う(バッド)プラクティス

概要

某OSSで見つけたテクニック。

bs = BookShelf('XX入門', '初めてのYY', '今さら聞けないZZ')
bs.map_(lambda: print(this))
stdout
XX入門
初めてのYY
今さら聞けないZZ

唐突に出現したthis。一体どこから生えてきたのか?

種明かし

コールバック呼び出し前に細工をしていた。

class Book:
    def __init__(self, name):
        self._name = name

    def __str__(self):
        return self._name

class BookShelf:
    def __init__(self, *book_names):
        self._books = [*map(Book, book_names)]

    def map_(self, callback):
        for book in self._books:
            callback.__globals__['this'] = book
            callback()

        del callback.__globals__['this']

分かってみれば大したこと無いが、
インタプリタやIDEの拡張を疑いしばらく悩みこんでしまった。

バッドプラクティスだと思う理由

次のように、引数として渡した方がPythonicだと思うから。

class BookShelf:
    def __init__(self, *book_names):
        self._books = [*map(Book, book_names)]

    def map_(self, callback):
        for book in self._books:
            callback(book)

bs = BookShelf('XX入門', '初めてのYY', '今さら聞けないZZ')
bs.map_(lambda this: print(this))

Zen of Python を大事にしたい。

Explicit is better than implicit.
暗示するより明示するほうがいい。

参考Qiita - プログラマが持つべき心構え (The Zen of Python)

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

リアルガチで言語別単価データを集計&比較してみた by Python

前々からフリーランス案件の言語別単価を比較&集計してみたかったので実際に調査してみました

フリーランス案件の情報は下記サイトを元に集計しました
各言語のページに集計情報があったのでそれを元にしています

レバテックフリーランス
https://freelance.levtech.jp

※2020年11月21日時点のデータです

Cloud.csv
created_at,skill,count,avg_price,max_price,min_price
20201121,OpenShift,5,62,65,60
20201121,Amazon VPC,1,70,70,70
20201121,Google Cloud Platform,55,80,105,53
20201121,Dynamics CRM,1,65,65,65
20201121,Amazon S3,2,80,85,75
20201121,Amazon EC2,2,74,85,64
20201121,AWS,1926,76,135,20
20201121,Google App Engine,7,77,95,60
20201121,Amazon SimpleDB,1,80,80,80
20201121,Force.com,2,70,70,70
20201121,Office 365,9,56,65,40
20201121,Microsoft Azure,141,67,90,50
20201121,OpenStack,21,64,80,45
20201121,Heroku,29,75,102,55

DB.csv
created_at,skill,count,avg_price,max_price,min_price
20201121,Kyoto Tycoon,1,75,75,75
20201121,SQLite,20,72,90,55
20201121,MySQL,1465,75,125,35
20201121,Symfoware Server,4,60,65,55
20201121,Cassandra,14,62,80,45
20201121,PostgreSQL,487,71,135,35
20201121,Riak,5,63,100,45
20201121,Bigtable,3,86,95,80
20201121,SQL Server,375,65,95,35
20201121,Redis,296,78,105,39
20201121,Hbase,5,68,75,60
20201121,Oracle,971,66,115,25
20201121,Sybase,6,75,85,65
20201121,IMS,3,58,64,55
20201121,Access,88,60,145,40
20201121,DB2,79,64,95,35

FrameWork.csv
created_at,skill,count,avg_price,max_price,min_price
20201121,Pyramid,12,77,105,65
20201121,Rails,237,81,115,45
20201121,MyBatis,61,71,85,55
20201121,Node.js,256,77,125,45
20201121,Backbone.js,77,76,115,39
20201121,Knockout.js,4,72,90,55
20201121,AngularJS,152,74,105,47
20201121,Laravel,343,73,125,40
20201121,JUnit,80,73,125,50
20201121,Wicket,3,82,95,70
20201121,Django,89,83,115,55
20201121,Padrino,2,82,85,80
20201121,iBATIS,14,67,80,55
20201121,Silex,4,76,80,75
20201121,FuelPHP,101,76,115,50
20201121,MVC,69,68,95,37
20201121,CodeIgniter,39,74,105,45
20201121,Liferay,1,70,70,70
20201121,React,378,77,115,40
20201121,Spark,19,82,95,55
20201121,PhoneGap,3,86,95,80
20201121,JSF,18,64,75,55
20201121,jQuery,354,71,95,35
20201121,Seasar2,32,70,95,49
20201121,Spring,362,71,115,45
20201121,Bottle,1,75,75,75
20201121,Catalyst,26,64,72,55
20201121,intra-mart,14,70,135,50
20201121,SAStruts,16,71,95,57
20201121,Flask,25,82,125,65
20201121,Sinatra,6,78,85,75
20201121,Struts,129,67,95,45
20201121,Symfony,55,74,95,55
20201121,CakePHP,149,71,95,50

Language.csv
created_at,skill,count,avg_price,max_price,min_price
20201121,PHP,1560,72,145,35
20201121,CSS,650,70,125,35
20201121,C言語,269,68,115,35
20201121,UML,7,70,85,50
20201121,C++,374,72,125,45
20201121,C#.NET,147,62,80,32
20201121,Transact-SQL,6,65,75,55
20201121,Kotlin,270,83,125,45
20201121,VB,83,62,85,40
20201121,HTML5,305,72,115,35
20201121,VC,8,61,66,50
20201121,C#,706,68,115,35
20201121,Apex,49,79,135,55
20201121,VBA,165,60,145,30
20201121,ASP.NET,161,65,95,48
20201121,Go,361,81,125,39
20201121,PL/SQL,146,63,85,45
20201121,COBOL,81,61,95,37
20201121,LESS,9,74,115,55
20201121,Swift,407,81,125,50
20201121,VC++,30,65,90,52
20201121,HTML,674,69,125,35
20201121,CSS3,209,75,115,35
20201121,Java,2484,70,145,25
20201121,JSP,54,68,105,55
20201121,Sass,55,71,115,45
20201121,SQL,881,65,145,33
20201121,VBScript,44,62,75,45
20201121,Objective-C,247,76,115,40
20201121,CoffeeScript,16,81,95,65
20201121,JavaScript,1749,72,125,20
20201121,Lua,6,73,80,65
20201121,R言語,19,78,95,65
20201121,Smalltalk,2,75,80,70
20201121,Scala,117,83,115,55
20201121,Perl,108,73,110,50
20201121,Shell,193,64,95,45
20201121,SAS,12,61,75,50
20201121,Ruby,652,80,125,39
20201121,ABAP,12,67,80,55
20201121,Python,669,78,145,32
20201121,VB.NET,284,62,85,40

OS.csv
created_at,skill,count,avg_price,max_price,min_price
20201121,Windows,998,63,135,25
20201121,Red Hat,92,66,95,40
20201121,Solaris,37,61,80,40
20201121,AIX,77,63,90,52
20201121,Ubuntu,39,75,90,45
20201121,Unix,236,69,100,40
20201121,AS/400,1,65,65,65
20201121,FreeBSD,3,70,75,62
20201121,HP-UX,21,62,75,55
20201121,Windows Server,387,62,100,33
20201121,Android,563,78,125,35
20201121,Linux,2266,69,135,30

グラフ画像出力ソースがこちら

print_graph.py
import datetime
import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

"""
共通変数・定数設定
"""
DATA_PATH = 'data'
now = datetime.datetime.today()
CATEGORIES = {
    'Language',
    'FrameWork',
    'DB',
    'OS',
    'Cloud'
}

"""
 メイン処理
"""
def main() -> None:

    make_date = now.strftime("%Y%m%d")

    # ディレクトリ作成
    os.makedirs(DATA_PATH + '/graphs/' + make_date, exist_ok=True)

    price_graph(make_date)
    count_graph(make_date)
    mix_graph(make_date)


"""
 単価グラフ作成
"""
def price_graph(make_date: str) -> None:

    for category_key in CATEGORIES:
        df = pd.read_csv(DATA_PATH + '/levtech/' + category_key + '.csv')
        df = df.sort_values(by=['avg_price'], ascending=False)
        labels = df['skill'].str.replace('[ぁ-んァ-ン一-龥]+', '', regex=True)  # 日本語削除
        y_list1 = df['avg_price']

        fig = plt.figure(figsize=(14, 7))

        ax1 = fig.add_subplot(1, 1, 1)
        ax1.bar(labels, y_list1, color='tab:blue')
        ax1.set_ylabel('price')
        ax1.set_title(category_key)

        # X軸を縦書き表示
        ax1.set_xticklabels(labels, rotation=90)

        # 棒グラフに数値追記
        for x, y in zip(labels, y_list1):
            ax1.text(x, y, y, ha='center', va='bottom')

        # plt.show()
        fig.savefig(DATA_PATH + '/graphs/' + make_date + '/levtech-' + category_key + '-price.png', bbox_inches='tight', format='png', dpi=300)


"""
 案件数グラフ作成
"""
def count_graph(make_date: str) -> None:

    for category_key in CATEGORIES:
        df = pd.read_csv(DATA_PATH + '/levtech/' + category_key + '.csv')
        df = df.sort_values(by=['count'], ascending=False)
        labels = df['skill'].str.replace('[ぁ-んァ-ン一-龥]+', '', regex=True)  # 日本語削除
        y_list1 = df['count']

        fig = plt.figure(figsize=(14, 7))

        ax1 = fig.add_subplot(1, 1, 1)
        ax1.bar(labels, y_list1, color='tab:orange')
        ax1.set_ylabel('count')
        ax1.set_title(category_key)

        # X軸を縦書き表示
        ax1.set_xticklabels(labels, rotation=90)

        # 棒グラフに数値追記
        for x, y in zip(labels, y_list1):
            ax1.text(x, y, y, ha='center', va='bottom', fontsize='small')

        # plt.show()
        fig.savefig(DATA_PATH + '/graphs/' + make_date + '/levtech-' + category_key + '-count.png', bbox_inches='tight', format='png', dpi=300)


"""
 単価&案件数グラフ作成
"""
def mix_graph(make_date: str) -> None:

    for category_key in CATEGORIES:
        df = pd.read_csv(DATA_PATH + '/levtech/' + category_key + '.csv')
        df = df.sort_values(by=['avg_price'], ascending=False)

        labels = df['skill'].str.replace('[ぁ-んァ-ン一-龥]+', '', regex=True)  # 日本語削除
        y_list1 = df['avg_price']
        y_list2 = df['count']

        fig = plt.figure(figsize=(14, 7))

        left = np.arange(len(labels))
        width = 0.3

        ax1 = fig.add_subplot(1, 1, 1)
        ax1.bar(labels, y_list1, width=width, color='tab:blue', label='price')
        ax1.set_ylabel('price')
        ax1.set_title(category_key)

        ax2 = ax1.twinx()
        ax2.bar(left + width, y_list2, width=width, color='tab:orange', label='count')
        ax2.set_ylabel('count')

        # X軸を縦書き表示
        ax1.set_xticklabels(labels, rotation=90)

        # 棒グラフに数値追記
        for x, y in zip(labels, y_list1):
            ax1.text(x, y, y, ha='center', va='bottom')

        for x, y in zip(left + width, y_list2):
            ax2.text(x, y, y, ha='center', va='bottom', fontsize='small')

        # 凡例を出力
        handler1, label1 = ax1.get_legend_handles_labels()
        handler2, label2 = ax2.get_legend_handles_labels()
        ax1.legend(handler1 + handler2, label1 + label2)

        # plt.show()
        fig.savefig(DATA_PATH + '/graphs/' + make_date + '/levtech-' + category_key + '-mix.png', bbox_inches='tight', format='png', dpi=300)


if __name__ == '__main__':
    main()

実際のグラフ

  • priceが平均単価(万円)
  • countが案件数です
  • 単価と案件数が混ざったグラフは、単価が高い順です

言語

単価別

levtech-Language-price.png

案件数別

levtech-Language-count.png

単価と案件数mix

levtech-Language-mix.png

トップはKotlinですね。今Androidエンジニアが不足していると聞くので納得。
web系なら案件数と単価で見るとRuby、Python、goがコスパ良さそうですね。
Javaはとりあえず単価が高いイメージがあったんですが、PHPの方が高いというのは意外、、、需要が高すぎるんでしょうか?

次にフレームワーク

単価別

levtech-FrameWork-price.png

案件数別

levtech-FrameWork-count.png

単価と案件数mix

levtech-FrameWork-mix.png

トップはPhoneGapですね。自分は初めて聞いたんですが、Nitobiが開発,公開しているクロスプラットフォーム・モバイルアプリケーションの開発フレームワークらしいです。
web系だとこれまたDjangoやRailsがコスパ良さそうですね。Reactはwebなりアプリ開発に使われているので案件数も単価も良さげ。Vueはサイト元に情報が無かったです。。

DB

単価別

levtech-DB-price.png

案件数別

levtech-DB-count.png

単価と案件数mix

levtech-DB-mix.png

トップはBigtableです。Bigqueryはよく聞くんですが、違いはBigqueryより高速に動く大規模DBみたいな感じらしいです。
DBに関してはコスパで言うとMySQL覚えておけば間違い無いですね。Oracle、SQLServerは意外と低い。どうして、、、

Cloud

単価別

levtech-Cloud-price.png

案件数別

levtech-Cloud-count.png

単価と案件数mix

levtech-Cloud-mix.png

単価トップはGCPですね!案件数でいくとAWSが圧倒的!とりあえずAWSは最低限覚えた方が良さそうですね
需要が高くなりすぎると単価が安くなる傾向が見られるので、これからのことを考えるとGCPも覚えた方がいいかも?
Azureは案件数も単価数も微妙ですね

感想

なかなか面白い結果になりました!
これからweb系やるならPythonが伸びしろあるかもです。実際触ってみて、環境構築が手軽。ライブラリが豊富。特にCSV→グラフ作成がこんなに簡単にできるのには感動しました!
わざわざhtmlやjsのライブラリを使ってゴニョゴニョするよりバックエンドでさくっと作って画像化出来るのがとてもイイですね〜

今回はあくまで案件サイトの一つを調査した結果なので参考程度にしていただけると!
VueやGraphql、Rustの情報が載っていなかったのでそこらへんも気になるところですね〜

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

深層強化学習2 強化学習の実装

Aidemy 2020/11/22

はじめに

 こんにちは、んがょぺです!バリバリの文系ですが、AIの可能性に興味を持ったのがきっかけで、AI特化型スクール「Aidemy」に通い、勉強しています。ここで得られた知識を皆さんと共有したいと思い、Qiitaでまとめています。以前のまとめ記事も多くの方に読んでいただけてとても嬉しいです。ありがとうございます!
 今回は、深層強化学習の2つ目の投稿になります。どうぞよろしくお願いします。

*本記事は「Aidemy」での学習内容を「自分の言葉で」まとめたものになります。表現の間違いや勘違いを含む可能性があります。ご了承ください。

今回学ぶこと
・強化学習の実装

強化学習の実装

環境作成

・「強化学習」のChapterでは、環境などを自分で定義していたが、今回は強化学習用のさまざまな環境を用意しているライブラリを使用して環境などを作成していく。
・使用するライブラリは「keras-rl」というもので、これはKerasOpenAIGym(Gym)というライブラリで構成される。今回はこれを使ってカートポールのデモDQNで学習する。

・まずは環境を作成する。方法は「env=gym.make()」とするだけで良い。引数には環境の種類を指定する。カートポールの環境は「"CartPole-v0"」と指定する。以降はenvインスタンスにアクセスすることで操作できる。
・今回のカートポールでは、行動は「カートを右に移動させる」「カートを左に移動させる」の2つであり、これを取得するには「env.action_space.n」とすれば良い。

・コードスクリーンショット 2020-11-19 10.31.55.png

モデルの構築

・環境を作成したら、Kerasの関数を使って多層ニューラルネットワークを構築する。モデルはSequentialモデルで構築する。次に、Flatten()で多次元の入力を一次元に変換する。入力の形「input_shape」には、カートポールの現在の状態を指定するため、「env.observation_space.shape」を使う。
・層の追加は「model.add()」で行えば良い。全結合層はDense、活性化関数の指定はActivation()で行う。引数には「relu」「linear」などを指定する。

・コードスクリーンショット 2020-11-19 10.32.22.png

エージェントの設定1 履歴と方策

・ここでは強化学習の本体であるエージェントの設定を行う。まずは、この設定に必要な履歴方策を設定する。(履歴はそのまま「過去にどのような行動をしたかの履歴」)
履歴「SequentialMemory(limit,window_length)」で設定できる。limitは記憶しておくメモリの数である。
方策については、ボルツマン方策をとる時には「BoltzmannQPolicy()」を使い、ε-greedy手法をとる時には「EpsGreedyQPolicy()」を使う。

・コードスクリーンショット 2020-11-19 10.41.15.png

エージェントの設定2

・前項の履歴と方策を使って、エージェントを設定する。DQNアルゴリズムが実装されている「DQNAgent()」を呼び出し、以下の引数を与えれば良い。
・引数には、モデル「model」、履歴「memory」、方策「policy」、行動数「nb_actions」、初めの何ステップを強化学習に使わないのかを指定する「nb_steps_warmup」を設定する。
・上記を「dqn」という変数に入れたとすると、「dqn.compile()」でエージェントの学習方法を指定する。第一引数としては最適化関数を指定し、第二引数としてはmetricsで表される評価関数を指定できる。

・コード(modelなどは前項のものを使用)スクリーンショット 2020-11-19 11.12.57.png

テストの実施

・前項のdqnエージェントを学習させるには「dqn.fit()」を使えば良い。引数としては、環境(コード上ではenv)、エピソード数「nb_steps」、可視化するかどうか「visualize」、ログを出力するかどうか「verbose」がある。
・エージェントに学習させたら、これをテストする。テストは、エージェントを実行し、実際どのぐらいの報酬が得られるかを評価する。これは「dqn.test()」で行える。引数は「dqn.fit()」と同じだが、エピソード数のみ「nb_episodes」とする。

まとめ

・ライブラリを使用して強化学習を行うときは、keras-rlを使う。
「gym.make()」で環境を作成したら、モデルを作成して層を追加していく。また、履歴方策を設定し、これらを使ってエージェントも作成する。
・作成したdqnエージェントを「dqn.fit()」で学習させ、「dqn.test()」でテストする。

今回は以上です。ここまで読んでくださり、ありがとうございました。

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

【 Python】InvalidSchema: No connection adapters were found for[画像URL]の対処方法

自分用のメモに記載しているので、文章はかなり雑です。

python.py
import requests as req

#まずは1枚の画像を取得するところから
test_url="https://papimo.jp/h/00031715/hit/view/605"
soup = BeautifulSoup(html, "html.parser")

img_url=[]
img_tags=soup.find("table",attrs={"class":"graph-some"}).find_all("img")[-1]
img_tag=img_tags.get("src")
img_url.append(img_tag)

reqData1 = req.get(img_url)
with open('/content/gdrive/MyDrive/Colab Notebooks/iland_img/605.png', 'wb') as file:
  file.write(reqData1.content)

テスト用でとりあえず1つダウンロードしたかっただけなので、for文で回す必要がないと思っていたけども。
実際実行してみると

python.py
InvalidSchema: No connection adapters were found for[画像URL]

というエラーが発生。
問題だったのはreq.getにリストをそのまま渡してしまっていたこと。
普通にfor文で回したら、解決

python.py
import requests as req

#まずは1枚の画像を取得するところから
test_url="https://papimo.jp/h/00031715/hit/view/605"
soup = BeautifulSoup(html, "html.parser")

img_url=[]
img_tags=soup.find("table",attrs={"class":"graph-some"}).find_all("img")[-1]
img_tag=img_tags.get("src")
img_url.append(img_tag)

for i_url in img_url:
  reqData1 = req.get(i_url)
  with open('/content/gdrive/MyDrive/Colab Notebooks/iland_img/605.png', 'wb') as file:
      file.write(reqData1.content)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自己相関係数の計算の方法

1 この記事は

自己相関係数をpythonで計算する方法を説明する。

2 方法

3日周期が存在するデータの自己相関係数を自動計算する。(plot_acfを使う)

sample.py
#dataを定義する。
from statsmodels.graphics.tsaplots import plot_acf
import pandas as pd
import numpy as np

dat = [
    ['07-01',1],
    ['07-02',10],
    ['07-03',20],
    ['07-04',2],
    ['07-05',11],
    ['07-06',21],
    ['07-07',3],
    ['07-08',22],
    ['07-09',32],
    ['07-10',4],
    ['07-11',23],
    ['07-12',33],
]

#datをDataFrame型変数dfに格納する。
df = pd.DataFrame(dat,columns=["A","B"])

print("dfを表示する","\n",df)

fig, ax = plt.subplots(ncols=2, figsize=(15, 4))
sns.lineplot(x="A", y="B", data=df,ax=ax[0])
plot_acf(df["B"].dropna(), lags=10, zero=False,ax=ax[1]) #自己相関算出時、NAがあると正しく計算できない。

147.JPG

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

Mendeley 上の論文読者数をPythonで取得する

はじめに

研究者の方々は、目にした/書いた学術論文の引用数が気になることがあると思います。
引用数はGoogle scholar 等で簡単に確認することができます。
引用数の前駆的な指標として、文献管理ソフトMendeley における読者数というものもあります。
こちらは少なくとも、Mendeleyを開かなければわからないように思えます。
そこで、スクレイピングの練習がてら、Mendeley reader 数を取得するスクリプトを作成してみました。

参考にした記事

[1] pythonでwebスクレイピング
[2] Webスクレイピングの注意事項一覧

環境

Windows
Python 3

スクリプト

全文は以下の通りです。
以下解説が続きます。

a.py
# Modules
import requests

# Constants
Mendeley = 'https://www.mendeley.com/catalogue/'
PaperID = []
PaperID.append("5a856ac7-0d75-3560-8824-9f9061f3eb50/")

# Functions
def SandwitchedText(text_source,text_1, text_2):
    return text_source.split(text_1)[1].split(text_2)[0]


for a in PaperID:
    r = requests.get(Mendeley + a)
    text = r.text

    print("Title : " + SandwitchedText(text, "\"title\":\"", "\",\"detail"))
    print("readers : " + SandwitchedText(text, "readers:", ":"))
    print("citations : " + SandwitchedText(text, "citations:", ":"))
  • requests はスクレイピングに使えるパッケージです[1]。使用時はスクレイピングのルールに注意しましょう[2]。
  • データを取得したい論文の情報として対応するURLを与えます。本記事では、有名な高温超伝導の論文を題材とします。スクリプト中変数Mendeleyの部分は固定で、URLのうち論文ごとに異なる部分をPaperIDのリストに与えてゆきます。
  • SandwitchedTextとして、与えられた文字列text_sourceからtext_1, text_2で挟まれた部分を返す関数を定義しています。
  • requests.get(url).text でURL に対応するページのソースを得ることができます。スクリプトでは、textの中にソースが文字列として格納されます。
  • 最後に、ページのソースから、論文のタイトル、mendeley reader 数、引用数を取得、コンソールに出力します。ここで、ページのソースとにらめっこして、ソース中どこに必要な情報があるかを探し、SandwitchedText関数を使って情報を取得します。

終わりに

リスト内の論文の数を増やしていけば、いくつかの論文の情報をまとめて得られます。
論文のURLではなく、タイトルを与えられるともう少しスマートだと思います。

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

Django 選択肢を自分の所属している施設のみにする

開発は、以前と同じペースで進めていますが、投稿がいまいちできていません。

今回は、何かを登録するときに、自分が所属している店のみ選択できるようにしようと思います。自分が、Pu-a-Puにのみ所属しているのに、希望シフトを登録するときに、間違えて縁宅って選んでしまうことを防ぐためです。

修正前

image.png

調べると、forms.pyとviewsの追記で対応できました。

views.py
    def get_form_kwargs(self):
        kwargs = super(KibouCreate, self).get_form_kwargs()
        kwargs['user'] = self.request.user
        return kwargs

独学でやっているためイメージがつかめていいないですが、form情報を取得する際に、自分自身の情報を渡して、formで処理した結果を取得してHtmlに返すってことだと想像しています。

forms.py
class kibouCreateForm(forms.ModelForm):
    class Meta:
        shift_object = Shift.objects.all()
        model = KibouShift
        fields = ('user', 'date', 'shift_name_1', 'shisetsu_name_1', 'shift_name_2', 'shisetsu_name_2', 'shift_name_3', 'shisetsu_name_3', 'shift_name_4', 'shisetsu_name_4')
        widgets = {
            'date': datetimepicker.DatePickerInput(
            format='%Y-%m-%d',
            options={
                'locale': 'ja',
                'dayViewHeaderFormat': 'YYYY年 MMMM',
                'ignoreReadonly': True,
                'allowInputToggle': True,
                }
            ),
        }
    def __init__(self, *args, **kwargs):
        #所属リストのみ選択可能とする
        user = kwargs.pop('user')
        UserShozoku_list = UserShozokuModel.objects.filter(user = user.id).values_list("shisetsu_name", flat=True)
        UserShozoku_list = list(UserShozoku_list)
        super(kibouCreateForm, self).__init__(*args, **kwargs)
        self.fields['shisetsu_name_1'].queryset = Shisetsu.objects.filter(id__in = UserShozoku_list).all()
        self.fields['shisetsu_name_2'].queryset = Shisetsu.objects.filter(id__in = UserShozoku_list).all()
        self.fields['shisetsu_name_3'].queryset = Shisetsu.objects.filter(id__in = UserShozoku_list).all()
        self.fields['shisetsu_name_4'].queryset = Shisetsu.objects.filter(id__in = UserShozoku_list).all()

    """
    締め日チェック
    """
    def clean_date(self):
        dt_now = datetime.datetime.now()
        dt_date = self.cleaned_data.get('date')
        #5日になったら20日以降のみ入力
        if dt_now.day > 5:
            startdate = datetime.date(dt_now.year,dt_now.month,20) + relativedelta(months=1)
            if dt_date < startdate:
                raise forms.ValidationError(
                "締め日が過ぎていますので、管理者にご連絡をお願いします",
                )
        else:
            startdate = datetime.date(dt_now.year,dt_now.month,20)
            if dt_date < startdate:
                raise forms.ValidationError(
                "締め日が過ぎていますので、管理者にご連絡をお願いします",
                )
        return self.cleaned_data.get('date')
    def clean(self):

        ############
        # 重複チェック
        ############
        dt_user = self.cleaned_data.get('user')
        dt_date = self.cleaned_data.get('date')
        if KibouShift.objects.filter(user=dt_user, date=dt_date).count() > 0:
            raise forms.ValidationError("同じ日の希望シフトがすでに登録されているので登録ができません。修正で対応をお願いします。")

        dt_shift_name1 = self.cleaned_data.get('shift_name_1')
        dt_shift_name2 = self.cleaned_data.get('shift_name_2')
        dt_shift_name3 = self.cleaned_data.get('shift_name_3')
        dt_shift_name4 = self.cleaned_data.get('shift_name_4')
        dt_shisetsu_name_1 = self.cleaned_data.get('shisetsu_name_1')
        dt_shisetsu_name_2 = self.cleaned_data.get('shisetsu_name_2')
        dt_shisetsu_name_3 = self.cleaned_data.get('shisetsu_name_3')
        dt_shisetsu_name_4 = self.cleaned_data.get('shisetsu_name_4')
        #シフトが休みなら施設は未入力とする
        if str(dt_shift_name1) == "休" or str(dt_shift_name1) == "有" or str(dt_shift_name1) == "不":
            if dt_shift_name2 != None or dt_shift_name3 != None or dt_shift_name4 != None or dt_shisetsu_name_1 != None or dt_shisetsu_name_2 != None or dt_shisetsu_name_3 != None or dt_shisetsu_name_4 != None:
                raise forms.ValidationError("シフト「休」「有」「不」の場合は、ほかの入力は不要です")

そうすると、
image.png

所属している施設だけになりました。

ついでに、スケジュールを追加する権限者以外は、ユーザー選択をできないようにしたいと思います。

少し追記しただけでできました。
っていっても、1時間ぐらい調べて実装ですが(笑)

本来は、選択されたユーザーごとに施設を選べることがベストだと思うのですが、javascriptでの実装か、ボタン押下で取得させる等、工夫が必要です。

今後、スキルアップしていく必要がありますね

forms.py
    def __init__(self, *args, **kwargs):
        #所属リストのみ選択可能とする
        user = kwargs.pop('user')
        UserShozoku_list = UserShozokuModel.objects.filter(user = user.id).values_list("shisetsu_name", flat=True)
        UserShozoku_list = list(UserShozoku_list)
        super(kibouCreateForm, self).__init__(*args, **kwargs)
        self.fields['shisetsu_name_1'].queryset = Shisetsu.objects.filter(id__in = UserShozoku_list).all()
        self.fields['shisetsu_name_2'].queryset = Shisetsu.objects.filter(id__in = UserShozoku_list).all()
        self.fields['shisetsu_name_3'].queryset = Shisetsu.objects.filter(id__in = UserShozoku_list).all()
        self.fields['shisetsu_name_4'].queryset = Shisetsu.objects.filter(id__in = UserShozoku_list).all()

        #選択されたユーザー毎の変更をする方がよいがジャバスクリプトかボタンで取得動作させるか必要
        #スケジュール追加権限があればユーザー選択を変更
        permissions = Permission.objects.filter(user=user)
        if user.has_perm('schedule.add_KibouShift'):
            self.fields['user'].queryset = CustomUser.objects.all()
        else:
            self.fields['user'].queryset = CustomUser.objects.filter(username=user.username).all()

ちなみに、もともと用意されているUserオブジェクトではなく、CustomUserに変更しました。データベースをクリアしてやり直したのでデータがパーになりました。

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

FlaskでHTTP環境変数

HTTP環境変数という言葉が合っているのかわからない。

app.py
from flask import Flask, request

app = Flask(__name__)


@app.route('/')
def hello_world():
    for k in request.environ:
        print("{}={}".format(k, request.environ[k]))
    return 'Hello World!'

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

SERVER_SOFTWARE=Werkzeug/1.0.1
REQUEST_METHOD=GET
SCRIPT_NAME=
PATH_INFO=/
QUERY_STRING=
REQUEST_URI=/
RAW_URI=/
REMOTE_ADDR=127.0.0.1
REMOTE_PORT=62784
SERVER_NAME=127.0.0.1
SERVER_PORT=5000
SERVER_PROTOCOL=HTTP/1.1
HTTP_HOST=127.0.0.1:5000
HTTP_CONNECTION=keep-alive
HTTP_CACHE_CONTROL=max-age=0
HTTP_UPGRADE_INSECURE_REQUESTS=1
HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36
HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
HTTP_SEC_FETCH_SITE=none
HTTP_SEC_FETCH_MODE=navigate
HTTP_SEC_FETCH_USER=?1
HTTP_SEC_FETCH_DEST=document
HTTP_ACCEPT_ENCODING=gzip, deflate, br
HTTP_ACCEPT_LANGUAGE=ja,en-US;q=0.9,en;q=0.8,und;q=0.7
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Anaconda】仮想環境をactivateする

概要

仮想環境をactivateします。

仮想環境の一覧を確認する

conda info -e

仮想環境の名前を確認したら、

仮想環境をactivateする

conda activate 仮想環境の名前

完了です。

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