474 Views
April 05, 24
スライド概要
[第7回大阪sas勉強会]三木悠吾
SAS言語を中心として,解析業務担当者・プログラマなのコミュニティを活性化したいです
Hash objectを利用した最小化法の実装 三木 悠吾
あらすじ とある会議にて、 STATの人「最小化法は結構プログラムの実行に1日とか時間がか かりますので、1回あたりのシミュレーションの回数も減らした いです。」 最初は営業戦略なのかと思ってました・・・ グローバルのプログラムを見たら、あまりにも仕様が糞! なのでついかっとなってhashでさらっと書いてみました。
遅い原因 • 仕様が糞(大事なことなので) • ランダムデータ生成した後、1obsごとに以下の処理をする • • • • レコードの因子に従って、ダミーテーブルを作成しデータを格納 ダミーテーブルのレコードをSQLで集計しマクロ変数へ取り込み マクロ変数をデータステップ内で評価しG1、G2を求める 乱数とG1、G2に従って割り付けを実施 ➢そりゃ遅いわな・・・
最小化法 • 最小化法は背景因子を調整しながら割り付ける動的割り付けの手法の一つ。 • 背景因子がある程度調整されるので、いい感じに集計できることが多い(偏ることもあるけど)。 • 完全なランダマイズではないのでエビデンスレベルは高くない 例 • この次にAge=12、male、OH=goodの人が来たとき、それぞれの群に割り付けられた時の背景因子の偏りを計算、 比較し、どちらの群に割り付けるか傾斜をつける。 • 症例数が少ない場合に有効
SASのINPUT/OUTPUT (I/O) • SASはlibnameなどでフォルダのデータを下記することができる。 つまり、こういうデータステップを書くとコンピュータのHDDからデータを読み込む作業、 コンピュータのHDDへデータを書き込む作業が発生する。 data work.adsl1; *HDDへ書き込み; set sdtm.dm; *HDDから読み込み; run; • HDDへの読み込み、書き込みはHDD内で物理的に針を動かすような作業をするから早くない。 • つまり、HDDへの読み書きを減らすことがプログラムの処理速度向上に直接影響する。
SASのhash object • SASYAMAさんの発表のためか、一部の界隈で人気。 • データステップに参照用のテーブル型のデータを組み込み参照したりすることができる。 • HDDではなくメモリ内に展開できるので、処理速度が速い。 • AEの日付とCMの日付見ながら、特定の薬剤治療見つけるなんてのはhashで一発! data ae3; set ae2; … if _n_ = 1 then do; declare hash hs1 (dataset:”cm2”); hs1.definekey(“USUBJID”, “CMSEQ”); hs1.definedata(“CMDECOD”, “CMSTDT”); hs1.definedone(); end; … run; • このコード(記載分)だとHDDへの読み書き回数は2*レコード数+1。 • 一方でもらったプログラムはかなり多い(数えるのが面倒なレベル)
実装編:乱数発生 data work01; do s = 1 to 10000; call streaminit(s); do i = 1 to 50; pid = 1000 + i; f1 = rand("table", 0.7, 0.3) - 1; f2 = rand("table", 0.5, 0.5) - 1; p = rand("uniform"); output; end; end; run; • 一応call streaminitで再現性確保。 • rand関数のテーブル指定は便利。F2 = rand(“table”,0.5,0.5)なら1と2が0.5、0.5の確率で生成される。 なお、rand(“table”,0.5,0.3)なら1と2と3が0.5、0.3、0.2の確率で生成される。
実装編:参照テーブル data g; length armn f1_v1 f1_v0 f2_v1 f2_v0 8.; call missing(of _all_); do armn = 1,2; output; end; run; • 最小化法のコアのGを計算するパラメータを格納することにした。 • これは背景因子の表を転置したようなデータが格納されます。
実装編:処理系 data work02; if 0 then set g; set work01 end = eof; by s i; if _n_ = 1 then do; declare hash hs (dataset:'g',ordered:'a'); hs.definekey('armn'); hs.definedata('f1_v1', 'f1_v0', 'f2_v1', 'f2_v0'); hs.definedone(); end; /* Initialize hash table */ if first.s then do; do armn = 1 to 2; f1_v1 = 0; f1_v0 = 0; f2_v1 = 0; f2_v0 = 0; rc = hs.replace(); end; end; • Hashのデータを途中で確認したいため、 end=eofにしておく。 • Hashの定義。一応orderedにしておく。 • Hashの初期化もマニュアルで実施。
実装編:処理系 /* calculate D/G in virtual arm*/ do armn = 1 to 2; rc = hs.find(); if armn = 1 then do; if f1 = 1 then a_f1 = f1_v1; ... if f2 = 0 then a_f2 = f2_v0; end; if armn = 2 then do; if f1 = 1 then b_f1 = f1_v1; ... if f2 = 0 then b_f2 = f2_v0; end; end; call missing(armn); ・ARMごとに背景因子の値を比較用変数に格納 ・このARMnは仮の値なのでここで削除 (厳密には不要だけど分かりやすくするため) /* g = d1 + d2 */ g1 = range(a_f1 + 1, b_f1) + range(a_f2 + 1, b_f2); g2 = max(a_f1, b_f1 + 1) - min(a_f1, b_f1 + 1) + max(a_f2, b_f2 + 1) - min(a_f2, b_f2 + 1); ・G、Dの計算。今回はrange法。Max-minで出してた けど、range関数という便利なものがあると後で知っ た。
実装編:処理系 /* Set ARM */ if g1 = g2 then do; if P <= 1/2 then armn else if P <= 2/2 then end; else if g1 < g2 then do; if P <= 4/5 then armn else if P <= 5/5 then end; else if g1 > g2 then do; if P <= 1/5 then armn else if P <= 5/5 then end; = 1; armn = 2; • Gに従って割り付けに傾斜をつける • Pに従って割り付ける = 1; armn = 2; = 1; armn = 2; /* feedback to hash table*/ rc = hs.find(); if f1 = 1 then f1_v1 = f1_v1 + 1; else if f1 = 0 then f1_v0 = f1_v0 + 1;.. rc = hs.replace(); run; • 割り付け結果でHashテーブルをアップデート
結果 • 100例、100,000回でも10-20秒くらいで終わるプログラムができ た。 • STATの人は沈黙を貫いていた・・・ • 遅いときはSAS IOの性質を理解して、HDDへの書き込み回数減 らすと物理で早くなります。