裏 RjpWiki

Julia ときどき R, Python によるコンピュータプログラム,コンピュータ・サイエンス,統計学

Julia に翻訳--006 正規確率紙に累積相対度数をプロットする

2021年02月25日 | ブログラミング

#==========
Julia の修行をするときに,いろいろなプログラムを書き換えるのは有効な方法だ。
以下のプログラムを Julia に翻訳してみる。

正規確率紙に累積相対度数をプロットする
http://aoki2.si.gunma-u.ac.jp/R/npp2.html

npp2 <- function(x) # データベクトル
{
        x <- x[!is.na(x)] # 欠損値を持つケースを除く
        n <- length(x) # データの個数
        x <- sort(x) # 昇順に並べ替える
        y <- (1:n-0.5)/n # 累積相対度数
        probs <- c(0.01, 0.1, 1, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 99.9, 99.99)/100
        plot(c(x[1], x[n]), qnorm(c(probs[1], probs[17])), type="n", yaxt="n",
                xlab="Observed Value", ylab="Cumulative Percent",
                main="Normal Probability Paper")
        points(x, qnorm(y))
        axis(2, qnorm(probs), probs*100)
}

ファイル名: npp2.jl  関数名:npp2

翻訳するときに書いたメモ

npp() よりは簡単?
指数乱数は randexp() だった。R はその他の分布型の乱数も用意されているのになあ。

==========#

using Distributions, Plots, Plots.PlotMeasures

function npp2(x; leftmargin=2mm, xlab = "観察値", ylab = "累積確率",
        main = "正規確率紙")
    pyplot()
    x = collect(skipmissing(x))
    n = length(x)
    sort!(x)
    y = (collect(1:n) .- 0.5) ./ n;
    qny = quantile(Normal(), y);
    probability = [0.01, 0.1, 1, 5, 10, 20, 30, 40, 50,
        60, 70, 80, 90, 95, 99, 99.9, 99.99]/100;
    qnp = quantile(Normal(), probability)
    scatter(x, qny,
        tickdirection=:out,
        grid=false,
        label="",
        xlabel=xlab,
        ylabel=ylab,
        title=main,
        leftmargin=leftmargin,
        ylims=(minimum(qny), maximum(qny)),
        size=(600, 400))
    yticks!(qnp, string.(round.(probability, digits=4)))
    hline!(qnp, linecolor = :gray, linestyle=:dot, label="")
end

using Random

Random.seed!(123)
x = randn(100) # 正規乱数
npp2(x)

Random.seed!(123)
npp2(rand(100)) # 一様乱数

Random.seed!(123)
npp2(randexp(100)) # 指数乱数

コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

Julia に翻訳--005 機械学習のプログラム

2021年02月25日 | ブログラミング

機械学習ということであるが,例のごとく「機械的に」train:test = 8:2 に分割し,1 回だけの分析である。
以下の 2 つの結論だけを述べている。
「調整済みの決定係数が 0.5777 であった。特別良くもないが悪くもない結果だった。」
「テストデータの RMSE を評価する。 8.143424」

機械学習は,もっと統計学的見地からの分析を踏まえてやっておかないと,足下をすくわれる。
今回のデータセットのサンプルサイズは 414 である。これを 8:2 に分割すると,テストデータはサンプルサイズが 83 しかなくなる。
わずか 83 例でモデルの評価ができるのか??
しかも,このデータセットの従属変数には外れ値がある。全体からすれば 1/414 = 0.2% の外れ値であるが,この外れ値が 83 個のテストデータに含まれるような場合には,一挙に 1/83 = 1.2% を占めることになる。
そのため,評価に多かれ少なかれ影響を及ぼすことになる。
元のプログラムを Julia に書き換えると 30 倍速くなる。いいかえれば,同じ時間で R の30倍の回数のシミュレーションを行える。

30万回のシミュレーションを行うと,最初の図に示すように RMSE のヒストグラムを描くと右に瘤のある分布になっている。
この瘤の原因が 1 個の外れ値である。外れ値がテストデータに含まれるとこの瘤に含まれるのである。RMSE が 10.3 以上になったのが 約 20% である。
トレインデータで得られたモデルが有用であるとしても,テストデータであまり好ましくない評価を得られる確率が 20 % もあるのは甚だ不適切な話だ。
今回のデータは,トレインデータで得られたモデルもさして有用でないので,論外ではあるが。

この 1 個の外れ値を除いて同じく30万回のシミュレーションを行うと,RMSE は綺麗な分布を示す。

この後の議論は何段階かあるのだが,結論は以下のようになろう。

数少ないデータを細切れにしてモデルを作成して,信頼性を評価しようとしても,全体としてモデルの信頼性はさらに低くなるだけである。
逆に,十分な数のデータがあるときには,そこからどのようにデータを抽出・分割してもだいたい同じような結果が得られる。それが確率というものである。
機械的に機械学習を行ってはいけない。

#==========
Julia の修行をするときに,いろいろなプログラムを書き換えるのは有効な方法だ。
以下のプログラムを Julia に翻訳してみる。

某 qiita の r タグにある機械学習のプログラム

library(rsample)
df <- read.csv("Real_estate_valuation.csv")

#coln <- colnames(df) #列名を保存しておく
#colnames(df) <- c("No","X1","X2","X3","X4","X5","X6","Y") #新しい名前を付けておく

set.seed(123)

sim = function() {
    df_split <- initial_split(df, prop = 0.8) #train:test = 8:2 の割合で分ける
    df_train <- training(df_split)
    df_test <- testing(df_split)
    df_train.lm <- lm(Y~X1+X2+X3+X4+X5+X6, df_train)
    summary(df_train.lm)

    predict2 = function(c, df){
        for (i in names(c)){
            if (i == "(Intercept)") { tmp <- c[i] }
            else { tmp <- tmp + c[i]*df[i] }
        }
        return(tmp[,1])
    }

    RMSE = function(m, o) {
        tmp <- sqrt(mean((m-o)^2))
        return(tmp)
    }

    c <- coef(df_train.lm)
    df_test.y <- predict2(c, df_test)
    df_test.rmse <- RMSE(df_test.y, df_test$Y)
    df_test.rmse
}

翻訳するときに書いたメモ

自前の predict2 は無駄である。既存の predict() を使えばよい。

==========#

using CSV, DataFrames, Query, GLM, Statistics, Plots, Printf

function sim(df, prop = 0.8)
    n = size(df, 1)
    m  = round(Int, n * prop)
    df_split = shuffle(collect(1:n));
    df_train = df[df_split[1:m], :];
    df_test = df[df_split[m+1:n], :];
    df_train_lm = lm(@formula(Y~X1+X2+X3+X4+X5+X6), df_train);
    df_test_y2 = predict(df_train_lm, df_test);
    sqrt(mean((df_test_y2 .- df_test.Y) .^2)) # RMSE
end

function sim0(df, trial=10000)
    n = size(df, 1)
    a = lm(@formula(Y ~ X1+X2+X3+X4+X5+X6), df)
    pred = predict(a);
    rmse0 = sqrt(mean((pred .- df.Y) .^ 2))
    R2 = r2(a)
    R2adj = adjr2(a)
    @printf("n = %d, mean(Y) = %.5g, RMSE = %.5g, R2 = %.5f, R2.adj = %.5f\n",
        n, mean(df.Y), rmse0, R2, R2adj)
    rmse = zeros(trial);
    for i in 1:trial
        rmse[i] = sim(df)
    end
    mean_rmse = mean(rmse)
    median_rmse = median(rmse)
    @printf("%d trials, Mean(RMSE) = %.5g, Median(RMSE) = %.5g)\n",
        trial, mean_rmse, median_rmse)
    pyplot()
    histogram(rmse, bins=50, label="", xlabel="RMSE", ylabel="Frequency")
    scatter!([rmse0], [0], label="\$RMSE_0\$")
    scatter!([mean_rmse], [0], label="\$Mean(RMSE)\$")
    scatter!([median_rmse], [0], label="\$Median(RMSE)\$")
    #savefig(@sprintf "sim%d.png" n)
end

using Random
Random.seed!(123);

df = CSV.read("Real_estate_valuation.csv", DataFrame, drop=[1]);
# names(df) # "X1", "X2", "X3", "X4", "X5", "X6", "Y"
trial = 300000

# 外れ値を含んだままのデータの分析
sim0(df, trial)
n = 414, mean(Y) = 37.98, RMSE = 8.7825, R2 = 0.58237, R2.adj = 0.57621
300000 trials, Mean(RMSE) = 8.8087, Median(RMSE) = 8.3622)

# 外れ値を除いたデータの分析
df2 = copy(df);
delete!(df2, 271); # 外れ値除去
sim0(df2, trial)
n = 413, mean(Y) = 37.788, RMSE = 7.9608, R2 = 0.62675, R2.adj = 0.62123
300000 trials, Mean(RMSE) = 8.0752, Median(RMSE) = 8.0537)

コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

PVアクセスランキング にほんブログ村

PVアクセスランキング にほんブログ村