2022年末

年の瀬、久しぶりに振り返りとしてブログを書くことにした。

コロナ禍で、やる気をなくしたりオープンな人間関係に不安を感じたりして、長らくブログを更新していなかったが、そろそろ再開してもいいかなと思ってきている。

理由はいくつかあるがざっと書くとこのあたりだろう。

  • 忙しさを言い訳に勉強時間が減っている。
  • アウトプットがプライベートに閉じていて強制力が足りない。
  • アウトプットしなくて誤魔化してやったことにできる習慣を打破したい。
  • アウトプットをもとに自分のモチベーションが刺激されるような関係を作りたい
  • わかっていない部分とちゃんと向き合いたい。

2023年はML・CSに向き合う一年にしたい。具体的にはこのあたりをきちんとおいたい。

  • データサイエンス系の最新のライブラリを使い慣れる
    • deepspeed
    • huggingface accelerate
    • polars etc
  • コンピュータサイエンス関連の基礎知識
    • Linuxの本
    • OS自作
    • 並行プログラミング入門
  • NLPの論文追随と今後に対する妄想
    • GPT系のチューニング
    • AI系のいろいろ
  • 基本的な理論の証明確認
  • エンジニアリングマネージャーとしても動き方

実際にどこまでできるかはわかりませんが、こうしたものの勉強し、適当にブログ更新でもできたらと思ってます。 ひとまずゆるくでも勉強して頻度高めに記事を公開できればと思います。

ABC142-D

AtCoder Beginner Contest 142のD問題の解説です。

問題の詳細は公式の問題ページをご確認ください。

問題文

正整数A,Bが与えられます。 AとBの正の公約数の中からいくつかを選びます。 ただし、選んだ整数の中のどの異なる2つの整数についても互いに素でなければなりません。 最大でいくつ選べるでしょうか。

制約

  • 入力は全て整数である
  • 1 ≤ A,B ≤ 10^{12}

入力

A B

出力

条件を満たすように選べる整数の個数の最大値を出力せよ。

解説

毎回、問題文を勘違いしやすいのですが、A,Bの公約数の作る集合の有限部分集合であって,任意の異なる2要素が互いに素となるようになるものを考える.その中で要素の数の最大値を求めればよいです.

まずA,Bの公約数は最大公約数の約数です. なので最大公約数の約数たちの中で条件を満たすものを選べばよいです。

個数が最大になる場合は,各要素は素数のべき乗になります。 なぜなら上の条件を満たす集合Sに対し,素数のべきでない要素xが存在したとします。 この時2つの互いに素な数y,zを使いx = yzとできます。 よってS - {x} + {y, z}を考えればこれはSよりも真に要素が大きくなります。

これからSは素数のべきしか要素がありません。 さらにSは最大の場合、最大公約数を割り切る素数のべきじょうの要素は全て入り、この時最大となります。

これから最大公約数を割り切る素数の個数が求める解になります。

それを計算すると以下になります。

from fractions import gcd
A, B = list(map(int, input().split()))

C = gcd(A, B)


def prime_factorize(n):
    a = {}
    while n % 2 == 0:
        a[2] = True
        n //= 2
    f = 3
    while f * f <= n:
        if n % f == 0:
            a[f] = True
            n //= f
        else:
            f += 2
    if n != 1:
        a[n] = True
    return a

print(len(prime_factorize(C)) + 1)

pythonで実装する場合の注意

  • gcdの計算方法
    gcdはmathモジュールにありますが、2020年3月現在AtCoderがサポートするpython 3.4.3では, fractionsモジュールを使う必要があります。

  • コピペの注意
    Pythonはインデントがずれるとエラーになります。 ローカルからのコピペミスで一回実行エラーに。。。 気をつけましょう。

Genieのメモ

JuliaのWebアプリケーションフレームワークGenieを試しに使って、最低限動くところを確認した後、諦めたのでそのメモ

ちなみに、挫折したprjはこれ

概要

  • Scaffold
    • 構築
    • View
    • Controller
    • Model
    • Routing
  • 挫折ポイント

構築

GenieはWeb applicationのフレームワークです。 ruby on railsのようなものです。

Scaffold自体の作成は

Genie.REPL.new_app("your_cool_new_app")

でできます. この時は,ここをコピーしているようです。

webアプリを起動する場合は以下のファイルをjulia samp.jl で起動すればいいです。

using Genie
import Genie.Router: route
fpath = "."
# ファイルパス
Genie.REPL.load_app(fpath)
Genie.config.run_as_server = true

route("/hello") do
  "Hello world!"
end

using Sampapp

# 0.0.0.0を指定しないとDockerでは動作しない
Genie.AppServer.startup(8000, "0.0.0.0")

Model

  • データベースの作成 REPL側で以下をする.
SearchLight.Generator.new_resource("Question")

これでmigrationファイルができる. migrationファイルを以下のように編集する.

module CreateTableQuestions

import SearchLight.Migrations: create_table, column, primary_key, add_index, drop_table

function up()
  create_table(:questions) do
    [
      primary_key()
      column(:question, :string)
      column(:author, :string)
    ]
  end

  add_index(:questions, :column_name)
end

function down()
  drop_table(:questions)
end

end
SearchLight.Migration.last_up()

で起動する.

モデルファイルの名前はテーブル名の複数形 - カラムの設定

  ### FIELDS
  id::DbId
  question::String
  author::String

  ### VALIDATION
  validator::ModelValidator
  • データの保存
Question(question = b[2], author = b[3]) |> SearchLight.save!
  • データベースの設定
dev:
  adapter: SQLite
  database: db/ask.sqlite
  host:
  username:
  password:
  port:
  config:

Controller

module QuestionsController

using Genie.Renderer
using Genie.Router
using SearchLight, Questions

function new()
  html!(:questions, :new)
end

function create()
  Question(question = @params(:question_precise), author = @params(:question_author)) |> save 
  redirect_to(:questions) 
end

function delete(id::Integer)
  SearchLight.delete(SearchLight.find_one!!(Question, id))
  redirect_to(:questions) 
end

function index()
  """
  :questions : questions resource(dir) 
  :questiontop: name of viewfile, questiontop.jl.html
  questions: @var :questions in jl.html
  """
  html!(:questions, Symbol("questiontop.jl.html"), questions = SearchLight.all(Question))
end

end # module

View

$(question.question)questionテーブルの question カラムの値

挫折ポイント

せっかく、ちゃんと動くとこまで確認したが、これ以上はもう限界だと思って諦めることにした。 具体的につらかったのは理由を一つずつあげていく

  • Genieが普通にひどいバグで起動しなかった時がある。
    • logに関するパスが設定がミスして、動かない等、本番環境で使うには課題も多数ある状態であった。

Genieをロードした段階で以下のエラーが出た。

  ERROR: LoadError: LoadError: UndefVarError: log_path! not defined
Stacktrace:
 [1] getproperty at ./sysimg.jl:13 [inlined]
 [2] load() at /Users/take/go/src/github.com/chaspy/ask/volume/samp_app/src/App.jl:204
 [3] top-level scope at none:0
 [4] include at ./boot.jl:326 [inlined]
 [5] include_relative(::Module, ::String) at ./loading.jl:1038
 [6] include(::Module, ::String) at ./sysimg.jl:29
 [7] include(::String) at ./client.jl:403
 [8] top-level scope at none:0
  • Postgresがうまく起動しない
    • LibPQの連携がエラーになり、正しくデータを埋め込めない。
  • Herokuで公開できない。
    • いろいろ課題があったが、そのままだとJuliaのPackageを読み込めず起動しない。
  • 挙動が遅い
    • 画面遷移も異様に時間がかかる。

実用にも現状耐えられず、使用感も悪かったため、挫折しました。 Julia自体はいい言語ですが、Web appのようにあまりなれていないものを使うにはとても大変でした。

ただ、この勉強で、Julia自体の挙動についてはかなり詳しくなりましたし、大規模アプリは少人数開発すると地獄を生むんだなと改めて実感しました。

SQL入門

最近SQLを使っているが,あまりに覚えられなくて苦労します。 そこで、忘備録を兼ねてよく使うものをまとめました。

バージョンはPostgres 10.5です。

テーブルの作成

  • 何もないところから作る
create table hoge (
  a varchar(255)
  , b smallint
)
  • データをselectして作る
create table  hoge
  as select * from hogehoge;

条件分岐

  • 等しいかをチェックする時
case a
   when b then  hoge
   when c then hoge1
   else hoge2
end
  • 一般の場合
case
  when a = b then hoge
  when a < c then hoge2
  else
    case
      when z > x then ggg
      when z = x then hhh
  end
end

まとめたりする操作

  • group byによる集約
select
  id
  , count(distinct a)
  , avg(b)
from hoge
group by id;
  • parition byによる分割

idごとに時間ごとのscoreの累積和を取る.

select
   id
   ,sum(score) over(partition by id order by starte_at
   -- 範囲を指定する場合の例
   rows between unbounded precedeing and 1 precedeing
   )
from hoge;

時間の操作

時間の型

  • timestamp
  • date
  • time
  • interval(時間の間隔)

さらにtimezoneの有無もある.

文字列を時間に

  • timestamp型
  select TO_TIMESTAMP('2000/01/01 20:15:00', 'YYYY/MM/DD HH24:MI:SS')
  • date型
  select TO_DATE('2000/01/01', 'YYYY/MM/DD')

フォーマットを合わせれば、形は多少自由でいける

時間を文字列に

  select to_char(now(), 'YYYY-MM-DD HH:MM:SS')

時間から、年、月、日を取得

  • date_trunc
  • extract
SELECT DATE_TRUNC('month', current_timestamp + interval '1 month') + '-1 day';

時間の足し算、引き算

timestamp型の場合,

  • timestamp型 - timestamp型
  • timestamp型 + '1 month'
  • timestamp型 - 数字はできなかった

time, date型

  • 同じ型同士の演算
  • time, date型 + interval'1 month'

多少慣れてきました. もう少し使いこなしたいものです.

wavファイルの再生

すること

  • 音そのものの再生
  • wavファイルから波形データを取得
  • wavファイルの音を再生

実行環境

  • データ
    声優統計コーパスを利用します.
  • 実行環境
    • Mac(High Sierra)
    • python3.7
    • Jupyter lab

実際に試したJupyter

まずwavファイルから音を再生します
初心者だったので,音はどういうデータとして扱われているかから調べました.

調べた結果,音声データは(シンプルには)1次元の配列として扱われていることがわかりました. 配列のi番目の要素の大きさが時刻iでの音の大きさを表します.

実際に,単純な正弦波を再生します.

import numpy as np
import IPython.display as ipd

rate = 8000
length = 1
t = np.arange(0, length, 1 / rate)
amp = 2.0
freq = 440
audio = amp * np.sin(2 * np.pi * freq * t)

ipd.Audio(audio, rate=rate)

次に実際のwavデータを読み込みます.
データを読み込む時は scipyscipy.io.wavfile を使います.

from scipy.io import wavfile
voice_path = "../voice-statistics/assets/data/tsuchiya_happy/tsuchiya_happy_001.wav"
fs, voice = wavfile.read(voice_path)

ipd.Audio(voice, rate=fs)

fsはサンプリング周波数,voiceは音声データの一次元配列です. voiceのサイズからフレーム数がわかり,フレーム数/ サンプリング周波数で再生時間もわかります.

注意
read時にWavFileWarning: Chunk (non-data) not understood, skipping it. WavFileWarning)が出ることがあります. おそらくwavのサンプリングレートとデータ本体以外のチャンクが捨てられたため,警告が出たのではないかと思います.

wavファイルのチャンクについてはここが参考になりました.

注意2
wavファイルにはステレオとモノラルの2つが(主に)あります. ステレオは二次元配列,モノラルは一次元配列になります. readipd.Audioもステレオ,モノラル両方対応しています. ただし,ステレオの場合,型が異なっており,転置が必要になります.

fs, stereo_audio = wavfile.read('./audios/signal.wav')
ipd.Audio(stereo_audio.T, rate=fs)

統計コーパスはモノラルなので特に意識する必要はありません.

Python3で学ぶ音声信号処理

最近は音声信号処理の勉強をしています。

音声信号処理にはaidiaryさんの素晴らしい記事があります.

ただ、これも昔の記事であるため、python2系で書かれているなど、最近の環境で適用するには使いづらい部分があります。

そこでpython3で自分の勉強を兼ねて記事をまとめてみようと思います。

  • Wavファイルの再生
  • フーリエ展開、フーリエ変換の一般論
  • 離散フーリエ変換
  • 高速フーリエ変換
  • 短時間フーリエ変換
  • ローパスフィルター
  • Grifin-lim
  • Wavenet
  • Waveglow

統計入門

統計の基本的な考えについて説明します.

統計はデータを用いて現象を分析するものですが、その統計にはざっくり言うと二つの方向性があります.

  • 統計的検定
  • 統計的推定

これらの違いはざっと言うと,統計的検定は仮説が正しいかをデータを用いて判断することであり、統計的推定はその結果をもとに将来(未知な部分について)を予測する方法です.

統計的検定

統計なので絶対はいえないですが、おそらく正しい、おそらく間違っているというのはあると思います。 そのために、統計的検定では以下のプロセスを行います.

  1. 仮説を作成する
  2. その仮説が正しいかどうかを判定する基準を設定
  3. 実際に現象に基づきその確率(=p値)を計算
  4. 2.の基準を超えているかどうかで仮説が否定されるかを判断する

典型的には5%を仮説が否定する基準になることが多いようです. 最近問題にもなっていますが p値 は仮説のただしさを保証する絶対的な基準ではないです.

例題1

コインを10回投げて表が2回出たという状況をかんがえましょう.

この時、先ほどの説明に基づき以下を設定します

項目
仮説 表が出る確率が50%
有意水準 5%

実際に起きた確率を計算すると  {45 + 2^9 ≒ 0.11} となり有意水準を上回るので.仮説は否定されないことがわかります.

例題2

コインを100万回投げて表が50万回出たとします.

この時、先ほどの説明に基づき以下を設定します

項目
仮説 表が出る確率が50%
有意水準 5%

実際に起きた確率はここを参照してもらうとして

おおよそその確率は {7.0 \cdot 10^{-4}} となります. 遥に0.05より小さいですね。 なので有意水準の考え方からするとこの仮説は否定されるわけです.

ではこの結果を元に仮説を否定するのが正しいのでしょうか? そこには明らかに問題があることがわかっていただけと思います

統計的推定

統計的推定では未知のデータに対して既存のデータを元に予測することをします.今の機械学習の手法とほとんど同じですね. 例えばベイズ推定や最尤推定がこうした話の基本になります.

なので、大抵の場合はある目的関数を最小化する関数を予測すると思ってくれればよいです.

具体的な話はまた今度します.

// コードブロック