盆暗の学習記録

データサイエンス ,エンジニアリング,ビジネスについて日々学んだことの備忘録としていく予定です。初心者であり独学なので内容には誤りが含まれる可能性が大いにあります。

モニターの輝度を一括・自動で調整するアプリを作った

背景

私は普段デスクトップPCで3枚のモニターに繋いだ環境で作業しています。

寝付きを良くするために夜はモニターの輝度を抑えたい派なのですが、ノートPCと違ってデスクトップPCだとWindows側から輝度を調整するメニューが無いためモニター側を手動で操作するしかなく、モニターが複数あるので面倒でした。

そこで

  • 複数のモニターの輝度を一括で操作したい
  • 輝度を自動操作したい:朝は明るく、夜は暗くしたい

という自分の要望を叶えるためのアプリを作りました

作ったもの

こんな感じの簡単なGUI付きアプリをPythonで作りました

「Schedule」のところをユーザーが調整可能で、画像の例だと朝9時に輝度100(最大)になって22時に輝度20になります。 実際に自分が使うときは100→20のような急激な変化はさせずに、もう少しなだらかに変化するように設定してます。

使った技術

モニターの輝度についてはmonitorcontrolというパッケージがあったので簡単に操作できました。

GUIについてはFletパッケージを使ってみたかったので今回採用しました。 Fletはサクッとアプリが作れて結構使いやすかったですが、細かいデザインまで調整しようとすると難しかったです。

Fletはまだまだ発展途上という感じで、機能も発展的なものはまだ無いのかなという印象でした。 例えばこのアプリをバックグラウンドで動作させたい(タスクトレイに最小化して入れておきたい)と思っているのですが、探した感じではそういう機能はまだなさそうでした。あとは新しいバージョンがリリースされたら自動検知させる機能とかも自分で実装しなければいけなさそう。

リポジトリ

ここに置いてます。

github.com

順序尺度にピアソンの積率相関係数を使うと相関を過小評価するおそれがある

最近、因子分析を勉強しています。

そのなかで順序尺度の相関係数という話題があったのでメモ。

たとえば、こんなデータがあったとします。

これは相関係数が0.5の二変量正規分布から発生させた乱数です。

これを「平均より高いかどうか」などテキトーな閾値で区切って離散化して質的変数にしてみます。

# 離散化
d1 = X[:, 0] >= X[:, 0].mean()
d2 = np.ones(shape=(n, ))
d2[(-4 <= X[:, 1]) & (X[:, 1] < 4)] = 2
d2[(4 <= X[:, 1])] = 3

クロス集計表は次のようになりました。

これらのデータに対して、

  1. 離散化前の量的変数に積率相関係数を適用した場合
  2. 離散化後の質的変数に積率相関係数を使用した場合

の結果はこのようになります

積率相関係数を離散化後の質的変数に適用している場合、0.353と真の値(0.5)よりもかなり小さな値になっています。

順序尺度の相関係数

こうした問題に対処するために、次のような相関係数が存在します。

今回でいうと2変数の両方とも離散化しているのでポリコリック相関係数を使うと、

このように真の値により近い推定値を得られます。

これらの相関係数は「順序尺度の背景には連続変数が存在し、どこかの閾値で区切られて順序尺度が作られている」と考え、その背景の連続変数が正規分布に従うと仮定して最尤推定していくものになります (このへんは小杉考司 2013がわかりやすいです)。

カテゴリ数がいくつだったら積率相関係数を使っていいのか

因子分析においてはカテゴリ数が5以上なら誤差が少ないという研究(萩生田伸子, & 繁桝算男 1996)を根拠に5がひとつの目安となる様子。

相関係数だけだとどうなのか気になったので軽く実験してみました。

また同様にデータを発生させます

pandas.cut()を用いて離散化させていきます。

for k in range(2, 11):
    d1 = pd.cut(X[0], bins=k, labels=range(k)).astype(int)
    d2 = pd.cut(X[1], bins=k, labels=range(k)).astype(int)
...

ポリコリック

今回のデータの場合、カテゴリ数が4くらいまでは積率相関係数の評価のズレが大きいように思えます。

その点、ポリコリックはよく機能しています。

ポリシリアル

2変数あったうち片方だけ離散化してポリシリアル相関係数を利用した場合、ポリシリアルのほうがズレてました…。なんでだろう。 まだまだ勉強不足のため原因に心当たりはありませんが、もし今後わかったら追記したいと思います…。

参考文献

豊田本はいい本でした。ただ絶版になっており、中古は値段が高騰しています…😢

[Javascript]HTMLや画像をコピーさせる

最近まで知らなかったのでメモ。

テキストのコピーの場合

そもそもコピーはどう実装するのか、文法の基礎を確認していきます。

以前であればdocument.execCommand("copy")を使っていたのですが非推奨になったようです。今はnavigator.Clipboardを使うようです。

テキストをコピーさせることに特化したwriteText()メソッドを使うと次のように書くことができます。

navigator.clipboard.writeText("Hogehoge").then(() => {
  console.log("success");
}, (msg) => {
  console.log(`fail: ${msg}`);
});

テキスト以外のコピーの場合

write()メソッドを使えば任意のオブジェクトをコピーさせることができます。

write() の使い方

write()ClipboardItemオブジェクトの配列を受け取ります。そしてClipboardItemBlobオブジェクトを受け取ります。

つまりどういうことかというと、次のように書きます。

const text = "ほげほげ"

// Blobには送りたいデータとMIMEタイプを入れる
const blob = new Blob([text], { type: "text/plain" });
// ClipboardItemにはMIMEタイプとblobを入れる
const data = [new ClipboardItem({ "text/plain": blob })];

// clipboard.writeにはClipboardItemの配列を入れる
navigator.clipboard.write(data).then(
  () => { console.log("success"); },
  (msg) => { console.log(`fail: ${msg}`); }
);

HTMLをコピーする

text/htmlのblobを用意してやることで、htmlで装飾された文章のコピー&貼り付けが実現できます。

メモ帳のようなテキストエディタに貼り付けた際はtext/plainで渡した文字列しか貼り付けられないため、貼り付けたいhtmlをtext/htmlにしてそのinnerTextに相当するものtext/plainに入れると良さそうです。

SlackなどのWYSIWYGエディタに貼り付ける際もtext/htmlだけでなくtext/plainも付けてやる必要があるようです(htmlのblobだけでは貼り付けても何も表示されませんでした)。

const copyHtml = (html, plainText) => {
  const blobHtml = new Blob([html], { type: "text/html" });
  const blobPlain = new Blob([plainText], { type: "text/plain" });
  const data = [new ClipboardItem({ "text/html": blobHtml, "text/plain": blobPlain })];

  navigator.clipboard.write(data).then(
    () => { console.log("success");},
    (msg) => { console.log(`fail: ${msg}`);}
  );
};

copyHtml('<a href="https://google.com/">Google</a>', 'Google')

画像をコピーする

blobを取得するために画像のURLに対してfetchする必要があったりとやや面倒ですが、以下のように書くことができます。

const img = document.getElementById("img");
const responsePromise = await fetch(img.src);
const blob = responsePromise.blob();
const data = [new ClipboardItem({ "image/png": blob })];

navigator.clipboard.write(data).then(
  () => { console.log("success"); },
  (msg) => { console.log(`fail: ${msg}`); }
);

参考までにhtml全体も載せておきます (fetchする際のCORSの問題を回避するためbase64で画像を載せています)

<!DOCTYPE html>
<html lang="en">
  <head></head>
  <body>
    <img id="img" src="data:image/png;base64,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" />
    <button id="copyButton">Copy</button>
    <script>
      const copyImage = async () => {
        const img = document.getElementById("img");
        const responsePromise = await fetch(img.src);
        const blob = responsePromise.blob();
        const data = [new ClipboardItem({ "image/png": blob })];

        navigator.clipboard.write(data).then(
          () => { console.log("success"); },
          (msg) => { console.log(`fail: ${msg}`); }
        );
      };

      const button = document.getElementById("copyButton");
      button.addEventListener("click", copyImage);
    </script>
  </body>
</html>

上記htmlのページからSlackに貼り付ける際は次のようになります

Electron + TypeScript + React の環境構築手順のメモ

Web開発の知識をそのままデスクトップアプリ開発に使えるということで、Electronが面白そうだな~と思っています。

jsに慣れている方であれば環境構築に悩むことはなさそうですが、筆者はReactもElectronも初心者なので環境構築の手順をメモしておきます

環境

  • OS: Windows 10 Home / 21H2 / 19044.2604
  • Node.js: v19.7.0

手順

1. Webpack + Typescriptの環境を作る

npm init electron-app@latest my-app -- --template=webpack-typescript

この状態でnpm startするとアプリはこんな感じ

2. tsconfig.jsoncompilerOptions"jsx": "react-jsx"を追加する

# tsconfig.json
{
  "compilerOptions": {
+    "jsx": "react-jsx",

3. reactのインストール

npm install --save react react-dom
npm install --save-dev @types/react @types/react-dom

4. コードの追加

src/app.tsxファイルを作って追加

import * as ReactDOM from 'react-dom';

function render() {
  ReactDOM.render(<h2>Hello from React!</h2>, document.body);
}

render();

src/renderer.ts に appの読み込みを追記

import './app';

npm startするとアプリは次のようになっている

参考

[OpenCV + tkinter]電子書籍の自作のための画像編集を行うアプリをPythonで作る

作業効率化のため、自分用に↑のようなアプリを作ってみました。

背景

私はとても狭い部屋に住んでいるため(&電子書籍が好きなので)、紙媒体でしか売っていない本を買った際は一度読んだら電子書籍を自炊しています。

電子書籍を自炊する際は

  1. 裁断する
  2. スキャンする
  3. 画像を編集する
  4. pdfにする

という手順で作業しているのですが、この「画像を編集する」の部分をpythonでやってみたのでどうやったのかメモしておきます。

マニアックな話題ですがもし刺さる人がいたらご覧ください。

画像の処理

ロジック

やりたいこととしては

1. 表紙(カバー)以外の画像に対して、色合いに補正をかけて文字がはっきり映るようにする
2. 全画像に対して、画像の横幅を同じサイズに揃える(縦横比は維持)

です。

表紙の処理について

表紙だけ処理を分ける理由

表紙(カバー)は基本的にツルツルの加工紙でできており、色合いが明瞭なままスキャンできます。 他方で本の中身のページは表面加工がない紙であることが多く、スキャンすると若干白っぽくなるため文字を色濃くしたいという意図があります。

表紙の判別ロジック

「モノクロ画像なら表紙とする」という判別方法をとってみることにします。 (フルカラーの本という例外もありますが数は少ないので一旦無視しています)

文字の色を濃くする処理について

具体的にどう処理するか考えていきます。

「文字の色を濃くしたい」と考えたとき、画像処理の素人の私は「コントラストを強くすれば…」と考えたのですが、画像処理でコントラストというと、もとの画像のピクセル  x \in [0,255] に対する、コントラスト(contrast)  \alpha明るさ(brightness) \beta による線形変換

 y = \alpha x + \beta

のことを指すようです(OpenCV Tutorials)。

また、ガンマ補正(gamma correction)

 y = \left( \frac{x}{255} \right)^{\gamma} \times 255

という非線形な変換もあるようです。

今回はガンマ補正を使うことにしました。理由は、今まで電子書籍を自炊するために画像を編集する際にはIrfanViewというアプリでgammaという項目を調整していて少し馴染みがあったのと、線形変換を少し試したもののαとβのちょうどいい値の組み合わせが簡単には見つからず、パラメータがγ一つですむガンマ補正のほうがパラメータの調整が簡単そうだったためです。

コード

画像の編集に関するコードは以下のようになりました。

from pathlib import Path
import numpy as np
import cv2
from PIL import Image


def gamma_correction(img: np.ndarray, gamma: float) -> np.ndarray:
    """ガンマ補正 (gamma correction)

    次のような変換を行う
        y = (x / 255)^gamma * 255
    """
    # 非線形関数(look up table)を作る
    look_up_table = np.empty((1, 256), np.uint8)
    for i in range(256):
        look_up_table[0, i] = np.clip(pow(i / 255.0, gamma) * 255.0, 0, 255)
    # 補正をかける
    return cv2.LUT(img, look_up_table)


def edit_image(input_path: Path, save_dir: Path, gamma: float = 1.6, new_width: int = 1080):
    # read
    # cv2.imread()/cv2.imwrite()はパスが日本語を含むとき文字化けしてエラーになるためPILを使う
    img = np.array([])
    with Image.open(input_path) as pil_img:
        img = np.array(pil_img)

    is_color = img.ndim == 3
    if is_color:  # カラー画像のときは、RGBからBGRへ変換する
        img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)

    # gamma correction
    if not is_color:
        img = gamma_correction(img, gamma=gamma)

    # resize
    height, width = img.shape[0:2]
    new_height = round((height / width) * new_width)
    img = cv2.resize(src=img, dsize=(new_width, new_height))

    # save
    save_path = str(save_dir / input_path.name)

    if is_color:  # カラー画像のときは、BGRからRGBへ変換する
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

    Image.fromarray(img).save(save_path)
    print(f"success: {input_path} -> {save_path}")

if __name__ == '__main__':
    source_dir = Path('./sample_data')
    save_dir = Path('./output')
    save_dir.mkdir(exist_ok=True)

    paths = list(source_dir.glob('*.jpg'))
    for path in paths:
        edit_image(input_path=path, save_dir=save_dir)

アプリ化

pythonスクリプト単体のままでは使いにくいので、GUIつきのアプリにしました。

GUIにするライブラリはpython標準ライブラリのtkinterを使うことにしました。flutterのラッパーのfletDearPyGuiなどのモダンなGUIライブラリも使ってみたかったのですが、「ファイルをドラッグ&ドロップできる」という機能が見当たらなかった一方でtkinterならドラッグ&ドロップが簡単に実装できそうだったので、楽な道を選ぶことにしました。

アプリ部分のコードはけっこう長くなってしまったのでGithubにあげておきます。

github.com

ビルドしたexeファイルもreleaseのところに置いておきますので、もしご興味がおありでしたらお試しください。

SHAP valuesの論文を読んだのでメモする

Lundberg, S. M., & Lee, S. I. (2017). A unified approach to interpreting model predictions. Advances in neural information processing systems, 30.

2017年の論文なので今更ですが読んだのでメモします。

沢山の内容を10ページに詰め込んだような全体的に説明が少ない論文だったので、ふわっとした理解のまま書いてます。誤情報があったらごめんなさい。

概要

  • 「この予測値はどういう特徴量が影響してこうなったのか」という局所的説明(local explanation)を行う手法
  • 特徴量の貢献度の推定量としてゲーム理論のShapley valuesの概念を使用することで望ましい性質を持つ推定量を開発(望ましいという根拠は不明)
  • 計算量が多いShapley valuesを重み付き最小二乗法で線形近似することで実用的な計算量にした

内容まとめ

additive feature attribution methods

この分野の先行研究として、LIMEやDeepLIFTのような既存の説明手法が存在する。これらは数学的には似ていて

のような線形のモデルで表現する形をとる共通点がある。

ここでφは特徴量の貢献度(attribution)で、z'は特徴量の有無を示す二値変数(z' ∈ {0 ,1}^M)であり、Mは特徴量の数で、iは列のインデックス。

本論文ではこうした手法を総称してadditive feature attribution methodsとよぶ。

先行研究たちの構造(線形、というモデルの構造)は一緒で、φの推定量が異なるような状況。

定量の性質とShapley values

このφの求め方は色々あるが、以下に述べる3つの性質を持つような解はひとつしか存在せず、その解はShapley valueである。

  1. Local accuracy: 説明モデルg(x')と元の予測モデルf(x)の出力値が一致する
  2. Missingness: ある特徴量が欠損しているとき、その特徴量は予測に貢献しない
  3. Consistency: 予測モデルf(x)の出力値を増加あるいは維持する(下げない)特徴量があるとき、その特徴量の貢献度も下がらない

(Missingnessはadditive feature attribution methodsであれば満たされる。Local accuracyとConsistencyはShapley valueに関する研究で性質が明らかになっている)

以下の式がShapley valuesとして知られるもので、これは特徴量を追加したときの予測値の変化を、特徴量の有無のすべての組み合わせにわたって加重平均したものである。

SHAP values

Shapley valuesを条件付き期待値で表現したもの。

Kernel SHAP

先行研究にはLIMEという局所線形近似によって説明モデルを作る手法がある。LIME推定量がShapley valueになるかどうかは損失関数L、重みカーネルπ、正則化項Ωに依存する。

以下のΩ, π, Lを使えば推定量がShapley valueになる

誤差関数は重みつきの二乗誤差 → 重み付き最小二乗法で推定できる。

数値例

「重み付き最小二乗法で求める」という方法について論文中ではさらっと説明が終わったので、こんな感じかな?と推測しながら書いたもの。

それっぽい値が出ているものの、なんか違う気もする。間違っている可能性が結構あるので話半分にみていただければと思います。

一番間違ってそうな箇所は予測値 y = f(h_x(z)) の部分で、特徴量が欠損している場合にどうやって予測させるかです。線形回帰なら特徴量をゼロとおけばいいけど欠損値を扱えない機械学習アルゴリズムの場合はこれができないので間違ってる気がする。でも正解の形がわからない・・・。

CSVとFeather, Parquetを比較してみる

最近Parquetというファイルフォーマットを知りました。S3にデータを置いてDWHを作ったりする際などに使うようです。

pyarrowパッケージをインストールしていればpandasからFeatherやParquetにお手軽に保存できることに気づいたので試してみてCSVと比較してみます。

比較方法

以下のようなランダムな値を入れたレコードを生成させてテストデータに使用します。

import random
from time import time
from string import ascii_letters
import pandas as pd
from pathlib import Path


def random_datetime():
    return pd.Timestamp(
        year=int(random.uniform(1990, 2020)),
        month=int(random.uniform(1, 12)),
        day=int(random.uniform(1, 28)),
        hour=int(random.uniform(1, 23)),
        minute=int(random.uniform(1, 59)),
    )

def gen_data(n=100):
    return pd.DataFrame({
        "int": list(range(n)),
        "float": [random.random() for _ in range(n)],
        "bool": [random.choice([True, False]) for _ in range(n)],
        "string": [''.join(random.choices(ascii_letters, k=10)) for _ in range(n)],
        "date": [random_datetime().date() for _ in range(n)],
        "datetime": [random_datetime() for _ in range(n)]
    })

そして、

  1. ファイルの保存(ディスクの書き込み)にかかった時間
  2. ファイルの読み込みにかかった時間
  3. ファイルサイズ

を比較してみます。

保存と読み込みは10回繰り返して平均をとります。

レコード数は [10_000, 100_000, 1_000_000, 10_000_000, 20_000_000] で試します。

保存時のオプション引数はデフォルトから変更せず(圧縮アルゴリズムなどは変えず)以下のように保存します。

df.to_csv('df.csv', index=False)
df.to_feather('df.feather')
df.to_parquet('df.parquet', index=False)

なお検証時のバージョンは以下の通りです。

  • python == 3.8
  • pyarrow == 7.0.0
  • pandas == 1.2.0

結果

保存速度

ファイルの保存にかかった時間は実数では上のグラフのようになりました。

f:id:nigimitama:20220305223754p:plain

CSVに比べた変化率は以下の表のようになります。

n feather parquet
10,000 -78.4% -69.6%
100,000 -95.0% -90.1%
1,000,000 -95.9% -93.9%
10,000,000 -96.0% -94.8%
20,000,000 -96.0% -94.8%

featherで78%~96%削減、parquetで70%~95%削減されました。レコード数が10,000のときが最も削減効率が悪かったです。

featherもparquetもcsvに比べると段違いに速いですが、featherのほうがparquetよりも若干速いですね。

読み込み速度

読み込みにかかった時間は次のグラフのようになります。

f:id:nigimitama:20220305223809p:plain

CSVに比べた変化率は以下の表のようになります。featherもparquetもcsvに比べると段違いに速いです。featherのほうがparquetより速いですね。

n feather parquet
10,000 -48.9% -22.0%
100,000 -73.0% -66.9%
1,000,000 -73.4% -71.3%
10,000,000 -70.0% -65.1%
20,000,000 -68.1% -53.4%

ファイルサイズ

ファイルサイズはCSVの半分程度になりました。

f:id:nigimitama:20220305222415p:plain

parquetはレコード数が増えるほどファイルサイズの削減効率が高まるようです。

n feather parquet
10,000 -47.2% -39.1%
100,000 -48.0% -41.9%
1,000,000 -48.7% -52.7%
10,000,000 -49.4% -54.6%
20,000,000 -49.8% -55.0%

(参考)グラフの元データ

上記のグラフの元となった具体的な数値の表も載せておきます。

n format write_mean write_std read_mean read_std size
10000 csv 0.044 0.001 0.013 0.001 0.683
10000 feather 0.010 0.009 0.007 0.000 0.360
10000 parquet 0.014 0.009 0.010 0.008 0.416
100000 csv 0.425 0.005 0.101 0.003 6.929
100000 feather 0.021 0.001 0.027 0.001 3.606
100000 parquet 0.042 0.002 0.033 0.004 4.024
1000000 csv 4.268 0.030 1.060 0.018 70.248
1000000 feather 0.175 0.007 0.282 0.004 36.026
1000000 parquet 0.261 0.008 0.304 0.006 33.239
10000000 csv 43.220 0.294 11.146 0.245 711.997
10000000 feather 1.709 0.023 3.346 0.108 360.235
10000000 parquet 2.257 0.050 3.895 0.106 323.421
20000000 csv 86.777 0.370 26.038 1.720 1434.600
20000000 feather 3.458 0.054 8.299 0.439 720.470
20000000 parquet 4.518 0.137 12.126 0.866 645.739

まとめ

データ分析時に前処理済みの訓練データの保存を行う際はcsvよりもfeatherやparquetのほうが良さそうですね。

「レコード数が極端に多いデータで、書き込み・読み込みの速度よりもファイルサイズの圧縮を重視したい」という場合はparquetのほうが良さそうですが、そうでない場合はfeatherが扱いやすそうです。