熊ノ平小屋は、最寄りの山である間ノ岳からも結構離れているので(山の地図によれば登りは3時間)、ご来光を見るにはロケーション的に難しいのですが、テラスからの朝風景はとても綺麗でした。夜の星も素晴らしく、本当に心地良い所でした。

 この日は熊ノ平小屋から北岳山頂を経由して広河原へ降りるルートです。前日よりも楽な工程ですし、広河原からのバスは伊那市バスとは違い夕方まで便数豊富なので特に急ぐ必要はないのですが、日曜日の夕方となると広河原は大混雑必至。小屋出発は早いに越したことはないので、夜明けとともに出て朝食は途中で、と当初は計画していたのですが、前日に朝食時間を伺うと4:30からと早め・・・それならばいただいてから出発しよう!と予定変更しました。朝食も夕食同様にとても美味しかったので正解でした。

 5時少し前に出発。間ノ岳まではずっと登りですが険しい部分はほとんどなく、あっさりと山頂に到着。昨年に標高が改定されて1m高くなり、日本3位タイに昇格した間ノ岳ですが、山標は変更されていないので以前の標高のまま。あくまでも個人的な印象ですが、相変わらずパッとしない山ですねえ(^^;)
 それでも北岳-間ノ岳ルートのルートに入ると、すれ違う登山者の数がずっと多くなりました。装備を見ると北岳山荘辺りに荷物を預けて間ノ岳までピストンしている人が結構多そうな感じです。間ノ岳ってそこまでして登る山ではない気がするんですが、やはり百名山になっている関係でしょうかねえ。なぜ深田久弥さんは間ノ岳を百名山に選んだのでしょう・・・選定ポイントは標高だけ?
 と、随分悪く書いてしまいましたが、間ノ岳は北岳を眺めるには最高のポイントだと思います。北岳はこちら側からのアングルが一番カッコよく見えます。
 
 今回のルートでは北岳山頂までの登りも結構楽でした。広河原から登る際の一番の難所は梯子が連続する八本歯のコル周辺なんでしょうが、難所といっても大したことはないので、北岳も塩見岳同様に難易度低めです(もちろん、北岳バットレスなどのバリエーションルートは別)。
 当然といえば当然ですが、北岳山頂は間ノ岳以上の賑わいでした。天気も良く眺望抜群。気持ち的なものですが、ここからの富士山の眺めは格別です! 富士山を見るには日本で一番高い場所なのですから・・・。

 広河原までの下りルートは、4年前に来た時には台風の影響で閉鎖されていた大樺沢コースを選択したのですが、当コースは渡渉がいくつもあるほか、道自体が沢になりかけている箇所も結構ありました。ゴアテックスなど防水性の高い登山靴でないと厳しいかも・・・特に登りで靴を濡らしてしまうと後が大変です。私は、今回は軽さ重視で普通のジョギングシューズ・・・奇跡的にほとんど濡らさずに走破出来ましたがコース選択を間違えました。

 毎日欠かさずジョギングをしていても、やはり登山では使う筋肉が違うので全身筋肉痛。いい運動したなあと実感します。

熊ノ平小屋の朝風景

熊ノ平小屋の朝風景

朝日を浴びる塩見岳

朝日を浴びる塩見岳

間ノ岳山頂付近

間ノ岳山頂付近

間ノ岳山頂

間ノ岳山頂。山標は旧標高のまま

間ノ岳より望む北岳

間ノ岳より望む北岳

北岳山頂

北岳山頂

賑わう北岳山頂

やはり北岳山頂は賑わっていました。

北岳山頂より富士山を望む

北岳山頂より富士山を望む

北岳山頂からの眺望 鳳凰三山

北岳山頂からの眺望
鳳凰三山

北岳山頂からの眺望 八ヶ岳

北岳山頂からの眺望
八ヶ岳

北岳山頂からの眺望 塩見岳

北岳山頂からの眺望
塩見岳

北岳山頂からの眺望 仙丈ヶ岳

北岳山頂からの眺望
仙丈ヶ岳

北岳山頂からの眺望 甲斐駒ヶ岳

北岳山頂からの眺望
甲斐駒ヶ岳

北岳バットレス

北岳バットレス
 

コメント   

 週末(7月25・26日)を利用して南アルプスの塩見岳・北岳を縦走してきました。
 塩見岳はずっと登りたいと思っていたのですが、登山口までのバスの運行期間が短い(今年は7/18~8/30)ことなどから、なかなかタイミングが合わず、今回ようやく実現出来ました。北岳は4年前の秋以来2回目です。
 
 鳥倉登山口行きバスの発着点である伊那大島駅までは、夜行の毎日あるぺん号を利用しました。毎日あるぺん号は各路線とも竹橋始発ですが、駒ヶ根・伊那大島・戸台口行きは八王子から乗車出来るので府中在住の私には便利です。中央道のルートでは駒ヶ根よりも伊那大島(松川インター)の方が遠いのに、当バスは最初に伊那大島駅に着きます(戸台口が中央道から離れている関係ですかね)。八王子発が24:15なのに3時過ぎには到着しました。結構近いですね。登山バスの出発時刻は6:45と時間が空いてしまうので、「同じ塩見岳行きの人が何人かいればタクシーに相乗りさせてもらおうかな」なんて淡い期待を持っていたのですが、伊那大島で降りたのは私一人・・・塩見岳人気ないんですかね? さすがに一人でタクシーに乗るのは気が引けるので(料金12,000円ですし、そもそも予約してません)、朝まで駅で仮眠・・・伊那大島駅は拍子抜けする程小さな駅で駅前には店の1件もありませんが、夜中でも開いていたので助かりました。

 鳥倉登山口までのバスの料金は2,490円。「確かネットで調べた時には1,600円くらいだったはず・・・記憶違いかな?」と思ったのですが、この日山小屋で一緒になった方から聞いた話では荷物代が50%加算されるとのこと。なるほど、大切な荷物なので子供料金相当は仕方なしということなのでしょう。まあ、伊那市も運営が大変なことでしょうしバス路線を維持してくれるだけでも感謝しないと・・・。
 バスは「よくこんな急坂を登れるなあ」なんて思うような道を通り、登山口到着は8時半くらい。塩見岳・北岳を2日間で縦走するとなると、宿泊地は熊ノ平小屋のほぼ一択となりますが、鳥倉登山口から熊ノ平小屋までの所要時間は地図によれば13時間弱。私の場合は機動力重視なので、そこまではかからないでしょうが9時間くらいは見込んでおいた方が安全。その場合、8時半スタートだと17:30着、暗くなる前には着けそうですが、これだと夕食を用意してもらえない可能性があるので、予め食料は多めに準備しておきました。
 熊ノ平小屋ではちょうど逆ルートで縦走されている方とお会いしましたが、伊那大島駅・松川インター行きのバスは鳥倉発14:25なので、そちらのルートの方が時間的にタイトですね(その方は広河原から熊ノ平小屋まで7時間で走破されたそうなので、きっと間に合ったことでしょう)。

 塩見岳山頂までの登山道は整備されていてとても歩きやすかったです。三伏峠小屋までは10分の1毎に案内板があり参考になります。山頂近くは岩がゴツゴツしていますが、鎖やロープが必要となるほどではなく、子供でも登れそうな感じです。ただ、今年は塩見小屋が建替工事で休業中なので、ゆっくり登山される方には工程的に少しキツイですかねえ。
 一方、山頂から熊ノ平小屋までのルートは登山者も多くないのか、倒木や生い茂った草で道が分かり辛くなっている箇所もありました。といっても、コースロスしてしまう程には荒れていませんから、悪天候でなければ危険度は低いです。

 そんな歩き易い道にも助けられ所要時間は6時間半少々。計画よりも随分早い15時過ぎに到着出来ました。熊ノ平小屋はトイレが離れた所にあるなど設備が最小限でこじんまりとした造りですが、とても居心地の良い所です。美味しいと評判の食事も噂に違わず素晴らしい! 過去に泊まった山小屋の中で最高かも。
 でも、この時期の土曜日にも関わらず宿泊客は15人程度とガラガラ。元々、メジャーなルート上に位置していないので、というのもあるのでしょうが、今年の場合は塩見小屋が利用出来ないことが影響しているかもしれません。三伏峠小屋まで(から)では遠すぎるとの理由で、このルートを避けてしまう人も結構いるのではないかと思います。
 お奨め出来る素晴らしい小屋なので、利用客が少なくて閉鎖なんてことにならないようもっと賑わって欲しいのですが、静岡市営なのでそんな心配は不要かな?
 しかし、静岡県の上に突き出た部分の先っぽに位置するこんな場所(下図参照)まで静岡市とは驚きです! さすがは静岡市、一時期は面積日本一でしたからねえ。
 ※平成の大合併の関係で、現在は5位まで下がってしまいました。

静岡県

熊ノ平小屋の位置。こんな所でも静岡市です!


 
 

伊那大島駅

伊那大島駅。小さな駅ですが夜中でも開いてました

鳥倉登山口

鳥倉登山口

塩見岳登山道

登山道は整備されていて歩きやすいです

三伏峠小屋までの案内板

三伏峠小屋までは10分の1毎に案内板があります

塩見小屋は建替工事中

塩見小屋は建替工事中。2016夏に再開予定
 

塩見岳

塩見岳。深田久弥が表現した「鉄兜」の言葉がまさにピッタリ。勇ましい山容です

塩見岳山頂付近

塩見岳山頂付近。ゴツゴツしてますが難易度は低め

塩見岳山頂(西峰)

塩見岳山頂(西峰)

塩見岳山頂(東峰)

塩見岳山頂(東峰)
こちらの方が5m程高いです

塩見岳山頂からの眺望(南東方面)富士山

塩見岳山頂からの眺望(南東方面)
富士山

塩見岳山頂からの眺望(南方面)

塩見岳山頂からの眺望(南方面)
荒川岳など

塩見岳山頂からの眺望(西方面)中央アルプス

塩見岳山頂からの眺望(西方面)
中央アルプス

塩見岳山頂からの眺望(北方面)仙丈ヶ岳・甲斐駒ヶ岳・北岳・間ノ岳など

塩見岳山頂からの眺望(北方面)
仙丈ヶ岳・甲斐駒ヶ岳・北岳・間ノ岳など

塩見岳山頂より甲斐駒ヶ岳を望む

塩見岳山頂より甲斐駒ヶ岳を望む
甲斐駒は夏でも白いので遠くからでもすぐに分かります

ちょっとしたお花畑

ちょっとしたお花畑

塩見岳(竜尾見晴付近より)

塩見岳(竜尾見晴付近より)

熊ノ平小屋

熊ノ平小屋

熊ノ平小屋の目の前にそびえる農鳥岳

熊ノ平小屋の目の前にそびえる農鳥岳

コメント   

 これまで、「D3.jsで空間分析にチャレンジ」の名目でボロノイ図カーネル密度推定IDW(逆距離加重補間)を作成してきましたが、次はいよいよ本丸のクリギングです。しかし、ここで完全に躓き、前回から随分と時間が開いてしまいました。クリギングはIDWとは比較にならないくらいに複雑で・・・。

 空間座標のばらつきを距離の関数として定義する「バリオグラム」という手法を用いるのですが、その関数を決定するパラメータ推定が難しい。R言語ではそれらを計算する関数が既に存在しているので、どの書籍を読んでもその手順には特に触れることもなく、当たり前のように計算しているのですが、Javascript(D3.js)ではそうはいきません。当初の目的(空間分析)からは完全に脱線しますが、最適化数学なんていう、馴染みのない分野を学習することにしました。

 遠い昔に学んだ(はずの)行列計算や偏微分の復習から始め、
 ・これなら分かる最適化数学 基礎原理から計算手法 金谷健一著 共立出版
 この本を参考に、何とか「ガウス・ニュートン法」という最適化手法までは比較的簡単にプログラム化出来たのですが、どうもこの手法は万能ではないようで関数最適化(パラメータ推定)に失敗することが多々。上記参考書にも紹介されている「レーベンバーグ・マーカート法」はガウス・ニュートン法の改良版でこれが良さそう・・・なのですが、この実装が難解を極めました。理論的にはさほど難しくないのですが、関数最適化(データと目的関数の誤差の最小化)へのプロセスが収束しているのか否かの判断が難しく試行錯誤の繰り返し。いろいろ調べてみると、RやC、Pythonなどにはレーベンバーグ・マーカート法のライブラリが存在しますが、これらのほとんどは、minpackという今から30年くらい前にFORTRANで書かれたプログラムを移植したものです。なので、これをJavascriptに移植してみることにしました。

 とりあえず移植版は完成し、数種類かのテストモデルにて試したところでは正しい結果を得られたのですが、このminpackはどういう手順で計算しているのかがさっぱりわかりません。ヤコビアン行列の計算あたりまでは、参考書にある手順と同じなのですが、その後でQR分解(?)などの処理が出てきます。その代わりに当手法(ガウス・ニュートン法もですが)では必須のはずの逆行列の計算が出てきません。逆行列を求めるのは複雑な処理なので、「ひょっとすると昔のコンピュータでは時間がかかり過ぎるためにQR分解で代用しているのかな?」なんて想像しているのですが、何しろ私には数学的バックボーンがないのでその考えが正しいのかどうかも判断分からず・・・。計算の趣旨が分からないプログラムをそのまま使用するのは納得いかないので、自作をチャレンジしてみました。

 前述しましたが、レーベンバーグ・マーカート法の実装で最も難しいのはパラメータ収束の判断です。minpackはこの部分も理解不能・・・ただ、ソース中にあるratio、actual、predictedといった言葉が関係していることは分かったので、これらのワードで検索をかけて文献を読みまくり(ほとんどが理論的なことしか記述されていません)、ついに実装出来そうな記述のある論文を見つけました。
 METHODS FOR NON-LINEAR LEAST SQUARES PROBLEMS 2nd Edition, April 2004
    K. Madsen, H.B. Nielsen, O. Tingleff
    Informatics and Mathematical Modelling Technical University of Denmark

 当文献に記載されている収束条件を採用したところ、最適計算の成功率が高まりました。パラメータ初期値の設定が適切でないと局所解に到達してしまう等で失敗する場合もありますが、それはminpackでも同じこと。テストした限りではminpackと同等程度の成功率が得られています。計算部分のソースは以下の通り。

/*
    LevenbergMarquardt.js

	Copyright (C) 2015, Yoshiro Tamura (http://www.eclip.jp/)
	All rights reserved.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or any
    later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

function EstimatedResult() {
    this.parameters = new Array();
    this.iteration = 0;
    this.residualsumsqueres = 0;
}

var lmresult = new EstimatedResult();

/*
  function LevenburgMarquardt

     Input Parameters
        datax : array of independent variables
        datay : array of observed variables
        p0    : array of initial guess values for parameters
        strfcn: string of model function
            The string must obtain x and p[0,1,2,...]
            e.g. p[0]*x/(p[1] + x)      (Michaelis-Menten kinetics)

     Return value
        true  : success to find a solution
        false : fail to convergence
*/
function LevenburgMarquardt(datax,datay,p0,strfcn) {

   var m = datax.length;
   var n = p0.length;
   var p = p0.concat();
   var epsilon1 = 1.0e-6;
   var epsilon2 = 1.0e-10;
   var strfcnder = new Array();
   var x;
   var i,j,k;
   var ss_lm,ss_gn,ss0;
   var iter,cntinner,cntouter;
   var itermax = 10000
   var cntinnermax = 1000;
   var cntoutermax = 1000;
   var delta_lm = new Array();
   var delta_gn = new Array();
   var p_lm = new Array();
   var p_gn = new Array();
   var g = new Array();
   var r = new Array();
   var h = new Array();
   var jac,jacT,jacTjac_lm,jacTjac_gn;
   var invjacTjac_lm,invjacTjac_gn;
   var mu = new Array();
   var muMax;
   var v = 2;
   var lambda0 = 0.0001;
   var lambda = lambda0;
   var lambdaMax = 1.0e+20;
   var lambdaMin = 1.0e-16;
   var lv = 0.01;
   var actual,predicted,ratio;
   var convergence;
   var terminateouter = false;
   var terminateinner;

   for (j = 0; j < n; j++) {
      mu[j] = 0;
   }

   convergence = false;
   iter = 0;
   cntouter = 0;

   while (!terminateouter) {
      ss0 = 0;
      for (i = 0; i < m; i++) {
         x = datax[i];
         ss0 += Math.pow(datay[i] - eval(strfcn),2);
      }
      for (j = 0; j < n; j++) {
         h[j] = Math.max(Math.abs(p[j])*epsilon1,epsilon2);
         strfcnder[j] = strfcn.replace(new RegExp('p\\['+j+'\\]','g'), '\(p\['+j+'\]+'+h[j]+'\)');
      }

      jac = new Array();
      jacT = new Array();
      jacTjac_gn = new Array();
      r = new Array();

      //Jacobian matrix
      for (i = 0; i < m; i++) {
         x = datax[i];
         jac[i] = new Array();
         r[i] = datay[i] - eval(strfcn);
         for (j = 0; j < n; j++) {
            jac[i][j] = -(eval(strfcnder[j]) - eval(strfcn)) / h[j];
         }
      }

      //transpose of Jacobian matrix
      for (j = 0; j < n; j++) {
         jacT[j] = new Array();
         g[j] = 0;
         for (i = 0; i < m; i++) {
            jacT[j][i] = jac[i][j];
            g[j] += jacT[j][i] * r[i];
         }
      }

      //Jacobian matrix transpose * Jacobian matrix
      for (j = 0; j < n; j++) {
         jacTjac_gn[j] = new Array();
         for (k = 0; k < n; k++) {
            jacTjac_gn[j][k] = 0;
            for (i = 0; i < m; i++) {
               jacTjac_gn[j][k] += jacT[j][i] * jac[i][k];
            }
         }
      }

      //copy array jacTjac_gn -> jacTjac_lm
      jacTjac_lm = JSON.parse(JSON.stringify(jacTjac_gn));

      terminateinner = false;
      cntinner = 0;
      while (!terminateinner) {
         for (j = 0; j < n; j++) {
            if (mu[j] == 0) {
               mu[j] = lambda * Math.abs(jacTjac_lm[j][j]);
            }
         }

         for (j = 0; j < n; j++) {
            jacTjac_lm[j][j] = jacTjac_gn[j][j] + mu[j];
            delta_lm[j] = 0;
            delta_gn[j] = 0;
         }

         invjacTjac_lm = InverseMatrix(jacTjac_lm);
         invjacTjac_gn = InverseMatrix(jacTjac_gn);

         p_lm = p.concat();
         p_gn = p.concat();

         for (i = 0; i < m; i++) {
            for (j = 0; j < n; j++) {
               for (k = 0; k < n; k++) {
                  delta_lm[j] -= invjacTjac_lm[j][k] * jacT[k][i]*r[i];
                  delta_gn[j] -= invjacTjac_gn[j][k] * jacT[k][i]*r[i];
               }
            }
         }

         for (j = 0; j < n; j++) {
            p_lm[j] += delta_lm[j];
            p_gn[j] += delta_gn[j];
         }

         ss_lm = 0;
         ss_gn = 0;
         for (i = 0; i < m; i++) {
            x = datax[i];
            ss_lm += Math.pow(datay[i] - eval(strfcn.replace(new RegExp('p\\[', 'g'),'p_lm\[')),2);
            ss_gn += Math.pow(datay[i] - eval(strfcn.replace(new RegExp('p\\[', 'g'),'p_gn\[')),2);
         }

         actual = (ss0 - ss_lm) / 2;
         
         predicted = 0;
         for (j = 0; j < n; j++) {
            predicted += 0.5*delta_lm[j] * (mu[j] * delta_lm[j]-g[j]);
         }

         if (Math.abs(predicted) > 0) {
            ratio = actual / predicted;
         } else {
            ratio = 0;
         }

         //check convergence
         if (ss_lm == 0 || (Math.abs(ss0 - ss_lm) / ss_lm < epsilon2 && Math.abs(ss_gn - ss_lm) / ss_lm < epsilon2)) {
            lmresult.iteration = iter+1;
            lmresult.parameters = p_lm;
            lmresult.residualsumsqueres = ss_lm;
            terminateouter = true;
            convergence = true;
            break;
         }

         muMax = 0;

         //successful iteration
         if (ratio > 0) {
            p = p_lm;
            terminateinner = true;
            iter++;
            cntouter++;
            for (j = 0; j < n; j++) {
               mu[j] = mu[j] * Math.max(1/3, 1 - Math.pow(2 * ratio - 1,3));
            }
            v = 2;
         //unsuccessful
         } else {
            for (j = 0; j < n; j++) {
               mu[j] = mu[j] * v;
               muMax = Math.max(muMax,mu[j]);
            }
            v *= 2;
            cntinner++;
         }

         if (!isFinite(muMax) || cntouter > cntoutermax) {
            for (j = 0; j < n; j++) {
               mu[j] = 0;
            }
            v = 2;
            lambda *= lv;
            if (lambda < lambdaMin) {
               lv = 100;
               lambda = lambda0 * lv;
            } else if (lambda > lambdaMax) {
               terminateouter = true;
               break;
            }
            terminateinner = true;
            p = p0.concat();
            cntouter = 0;
         }

         if (cntinner > cntinnermax) {
            return false;
         }

      }

      if (iter > itermax) {
         terminateouter=true;
      }
   }

   return convergence;

}

function  InverseMatrix(matrix) {

   var idm = new Array();
   var mm = new Array();
   var invertm = new Array();

   for (var i = 0; i < matrix.length; i++) {
      idm[i] = new Array();
      for (var j = 0; j < matrix[i].length; j++) {
         if (i == j) {
            idm[i].push(1);
         } else {
            idm[i].push(0);
         }
      }
      mm[i] = matrix[i].concat(idm[i]);
   }

   for (var i = 0; i < mm.length; i++) {
      pivotMatrix(i,mm);
      GaussJordanElimination(i,mm);
   }

   for (var i = 0; i < mm.length; i++) {
      invertm[i] = mm[i].slice(matrix.length);
   }

   return invertm;

}

function pivotMatrix(lNo,matrix) {

   var vMax;
   var curlNo=lNo;
   var curV;
   var i,j;

   vMax = Math.abs(matrix[lNo][lNo]);
   for (i = lNo+1; i < matrix.length; i++) {
      if (vMax < Math.abs(matrix[i][lNo])) {
         curlNo = i;
         vMax = Math.abs(matrix[i][lNo]);
      }
   }

   if (curlNo > lNo) {
      for (i = 0; i < matrix[lNo].length; i++) {
         curV = matrix[lNo][i];
         matrix[lNo][i] = matrix[curlNo][i];
         matrix[curlNo][i] = curV;
      }
   }

}

function GaussJordanElimination(lNo,matrix){

   var v,vv;

   v = matrix[lNo][lNo];

   for (var j = 0; j < matrix[lNo].length; j++) {
      matrix[lNo][j] = matrix[lNo][j] / v;
   }

   for (var i = 0; i < matrix.length; i++) {
      if (i != lNo) {
         vv = matrix[i][lNo];
         for (var j = lNo; j < matrix[lNo].length; j++) {
            matrix[i][j] = matrix[i][j] - vv * matrix[lNo][j];
         }
      }
   }
}

 GNUライセンスですので、ご自由に利用・加工していただいて結構ですが、数学の素人が作成しているので「ある一定の条件では正しく計算されない」といった不具合があるかもしれませんのでご了承下さい。不具合報告歓迎します。
 
 上記プログラムを利用した最適化計算(レーベンバーグ・マーカート法)サンプルのページも作成しました。
 ・Nonlinear Regression (Levenberg–Marquardt algorithm)

 今回は完全に逸脱してますが、本来のプロジェクトは「D3.jsで空間分析にチャレンジ」ですので、データ点のプロット及び求めた関数の描画も出来るようにしました。ソースダウンロード(ZIP圧縮)はこちら

コメント