クイックノート

ちょっとした発見・アイデアから知識の発掘を

Outlookで削除できない「標準アカウント」を消す方法

もう使わなくなったメールアカウントをOutlookから削除しようとしたら、
どうしても削除できないという現象が発生しました。

削除しなくても害がなければそのまま放置もありなのですが、
使ってないアカウントに対して毎回認証が要求されて、
結構なストレスのある状況でした。

ここでは、その解決方法を紹介します。

アカウントが削除できない問題

通常であれば、アカウントの削除は、
Outlook上で「ファイル」→「情報」タブ→「アカウントの設定」から、
削除したいアカウントを選択して、「削除」をクリックします。

f:id:u874072e:20200519093328p:plain
アカウントの削除

ところが、この方法でアカウントが削除できない場合があります。

エラーメッセージ

今回は、アカウントを削除する際に、
次のエラーメッセージが表示されました。

「プロファイルに標準アカウント以外のアカウントが存在する場合、標準アカウントを削除できません。標準アカウントを削除する前に、他のすべてのExchangeアカウントを削除する必要があります。」

「標準アカウント」という謎のキーワードが表示されていて、
この「標準アカウント」は削除できないようです。

メールアカウントの設定には「既定のアカウント」というものがあり、
メールを送信する際にデフォルトで利用するアカウントを設定できますが、
「標準アカウント」と「既定のアカウント」は別物です。
削除しようとしているメールアカウントを既定のアカウントから外しても、
やはり削除することはできません。

標準アカウントの意味

エラーメッセージでそのまま調べても、
「標準アカウント」が何を意味しているのかが分かりませんでした。

恐らくメッセージの翻訳がまずいのだろうと思い、
英語で同様のエラーを検索してみたところ、
以下の英語版では以下のエラーメッセージが表示されるようです。

「The primary account cannot be removed unless it is the only account in the profile. You must remove all other Exchange accounts before removing the primary account account.」

「標準アカウント」というのは「primary account」に対応していることが分かります。
「primary」は「最初の」という意味があり、
Outlook最初に登録されたExchangeのアカウントを指します。

つまり、Exchangeのアカウントが二つ以上存在する場合、
最初に追加されたExchangeアカウントは、
他のExchangeアカウントを全て削除しない限り削除できない

というのが今回の問題の原因だと分かりました。

解決方法

上のエラーメッセージの意味に従うと、
他のExchangeのアカウントを全て削除することで、
元々削除したかったExchangeのアカウントが削除できます。

ところが、他のアカウントは利用中なので、
削除するのには抵抗があります。

Outlookのアカウントの一覧は「プロファイル」という形で管理されています。 そこで、今のプロファイルを残したまま、
新しいプロファイルに必要なアカウントだけを追加するのが良さそうです。

ということで、その手順を紹介します。

  1. コントロールパネルを開く
  2. 「ユーザーアカウント」→「メール」をクリック
  3. 「プロファイルの表示」をクリック
    f:id:u874072e:20200519100413p:plain:w300
    メール設定
  4. 「追加」をクリックして新しいプロファイルを作成する
    f:id:u874072e:20200519100542p:plain:w300
    プロファイルの一覧
  5. 画面に従って必要なアカウントを追加する
    f:id:u874072e:20200519100801p:plain:w300
    アカウントの追加
  6. 新しいプロファイルを「常に使用するプロファイル」に選択する
    f:id:u874072e:20200519101012p:plain:w300
    使用するプロファイルの設定
  7. 「OK」をクリックしてOutlookを再起動する

これで新しく作成されたプロファイルに追加されたアカウントのみが読み込まれます。
元に戻したい場合は、使用するプロファイルを最初のものに戻せばOKです。

まとめ

Outlookで削除できない「標準アカウント」を、
取り除く方法について紹介しました。

今回の教訓は、エラーメッセージの意味が分からない時には、
対応する英語版のエラーメッセージも調べてみると良いということですね。
翻訳の過程で「primary(最初の)」という情報が欠落していたのが、
トラブルシューティングを難しくしているように思いました。

ML-Agentsを使ってみる

Unityでは強化学習をサポートしてくれる
ML-Agentsというツールが用意されています。

これで、誰でも簡単に強化学習ができる・・・
のですが、動かすまでが結構大変だったりします。

使い方を解説しているサイトも沢山あったのですが、
どれも若干情報が古いらしく、
そんな設定見当たらないというところが多くありました。

最新の情報は公式のドキュメントを当たるのがベストですが、
少々分かりづらかったので、
自分なりにチュートリアルを実行する手順をまとめ直してみました。

インストール

準備が必要なのは以下の三つです。

  • ML-Agentsのダウンロード
  • mlagentsのインストール
  • Barracudaのインストール

順に見ていきましょう。

ML-Agentsのダウンロード

githubからファイルをダウンロードします。

pythonライブラリのインストール

pythonで必要なライブラリをインストールしておきます。

pip3 install mlagents

を実行します。

Unityパッケージのインストール

Unity上で「Window」→「Package Manager」をクリックし、

f:id:u874072e:20200115155937p:plain:w300
package manager

「Advanced」→「show preview packages」をクリックします。

f:id:u874072e:20200115160130p:plain
show preview packagesにチェック

検索ボックスを活用して「Barracuda」パッケージをインストールします。

f:id:u874072e:20200115160304p:plain
Barracudaをインストール

プロジェクトの作成

チュートリアルに沿って、
新しく学習する環境を作って、
エージェントに行動を学習させてみます。

Player Settings

「Edit」→「Project Settings」->「Player」から、
「Display Resolution Dialog」を「Disabled」に設定しておきます。

f:id:u874072e:20200116133327p:plain:w300
Display Resolution DialogをDisabledに設定

アセットの読み込み

ダウンロードしたフォルダから、
ml-agents-master/UnitySDK/Assets/ML-Agentsを、
プロジェクトのアセットフォルダにドラッグアンドドロップします。

f:id:u874072e:20200115160757p:plain
アセットフォルダにドラッグ&ドロップ

オブジェクトを配置

ML-Agentsのチュートリアルにしたがって、
Plane,Cube,Sphereを配置します。

f:id:u874072e:20200115163142p:plain
オブジェクトを配置

Agentコンポーネントの追加

SphereにRigidbodyを追加して、
学習用のスクリプトを新しく追加します。
ここでは、「testAgent」という名前にしました。

f:id:u874072e:20200115163939p:plain:h300
学習用のエージェントスクリプトを新規作成

スクリプトの記述

学習エージェントの詳細な挙動を「testAgent」に記述します。

Agentクラスの継承

基本となるスクリプトの形を下に示します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MLAgents;

public class testAgent : Agent
{
    Rigidbody rb;

    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }
}

MLAgentsを読み込み、
Agentクラスを継承します。

Update()メソッドは削除して、
Start()メソッドのみを残します。
最低限の挙動としてRigidbodyだけ取得して変数に代入しています。

エージェントの動きは、この後実装するメソッドの中で記述していきます。

AgentReset

学習のために環境を反復する必要があります。
環境のリセットをAgentReset()の中で記述していきます。

    public Transform Target;
    public override void AgentReset()
    {
        if (this.transform.position.y < 0)
        {
            // If the Agent fell, zero its momentum
            this.rb.angularVelocity = Vector3.zero;
            this.rb.velocity = Vector3.zero;
            this.transform.position = new Vector3(0, 0.5f, 0);
        }

        // Move the target to a new spot
        Target.position = new Vector3(Random.value * 8 - 4,
                                      0.5f,
                                      Random.value * 8 - 4);
    }

Targetは後でUnity上で設定するターゲットオブジェクトです。

この場合は、エージェントが落下した場合や、
ターゲットに到達した場合に、
エージェントやターゲットの位置を配置し直しています。

CollectObservation

エージェントは、現状を入力として、
行動を選択します。

現状を把握するために用いる情報を、
CollectObservation()で定義します。

    public override void CollectObservations()
    {
        // Target and Agent positions
        AddVectorObs(Target.position);
        AddVectorObs(this.transform.position);

        // Agent velocity
        AddVectorObs(rb.velocity.x);
        AddVectorObs(rb.velocity.z);
    }

AddVectorObs()を使って取得する情報を加えていきます。
上の場合、ターゲットとエージェントの座標、
エージェントの速度の情報を使って行動を決定することになります。

AgentAction

エージェントの動作はAgentAction()で記述します。
ここでは、行動と終了判定及びその結果の報酬を定義します。

    public float speed = 10;
    public override void AgentAction(float[] vectorAction)
    {
        // Actions, size = 2
        Vector3 controlSignal = Vector3.zero;
        controlSignal.x = vectorAction[0];
        controlSignal.z = vectorAction[1];
        rb.AddForce(controlSignal * speed);

        // Rewards
        float distanceToTarget = Vector3.Distance(this.transform.position,
                                                  Target.position);

        // Reached target
        if (distanceToTarget < 1.42f)
        {
            SetReward(1.0f);
            Done();
        }

        // Fell off platform
        if (this.transform.position.y < 0)
        {
            Done();
        }

    }

把握した現状から導かれた選択がvectorActionに渡されます。
このvectorActionを使ってUnity上のオブジェクトを動かします。
上のスクリプトではvectorActionを動力に設定しています。

また、1サイクルの終了条件として、

  • ターゲットに到達した場合
  • エージェントが落ちた場合

を設定しています。
終了条件に合致した場合はDone()を呼び出してサイクルを終了しています。

ターゲットに到達した場合は報酬をsetRewardで報酬を与えています。

Targetの設定

再びUnity上での操作に戻ります。
testAgentのターゲットとして、
Cubeを選択します。

f:id:u874072e:20200116140248p:plain
ターゲットの設定

Behavior Parameterの追加

エージェントの設定を行うために、
Behavior Parametersコンポーネントを追加します。

f:id:u874072e:20200116133710p:plain:h300
Behavior Parameterの追加

エージェントの入力と出力に合うように設定します。

f:id:u874072e:20200116154052p:plain:w300
Behavior Parameterの設定

  • Vector ObservationのSpace Size : 観測する情報の数(今の場合、ターゲットの座標(3)+エージェントの座標(3)+x,z軸のエージェントの速度(2)で8個)
  • Vector ActionのSpace Type: 行動の変数型(今回の場合、動力を表すので連続値(Continuous)
  • Vector ActionのSpace Size: 行動の変数の数(今回の場合、x,z軸の動力なので2個)

学習の実行

環境の準備とエージェントの定義が終わったら、
いよいよ学習を実行します。

学習の実行には、まずターミナル上で、
学習のプロセスを走らせて、
その後、Unityの再生ボタンを押すことで開始します。

mlagents-learnプロセスの実行

学習を行うには、一旦Unityから離れて、
ターミナル上でmlagents-learnを起動する必要があります。

起動コマンドは下の通りです。

mlagents-learn  config/trainer_config.yaml  --run-id=test-1 --train

config/trainer_config.yamlは学習の設定ファイルです。
ダウンロードしたml-agents-masterの中にある
デフォルトの設定ファイルconfig/trainer_config.yamlを指定しています。

--run-idには学習結果を区別するための名前を指定します。

Unityで再生ボタンをクリックする

コマンドを実行すると、
下のような画面が表示され、
「Start trainning by pressing the Play button」のメッセージが現れます。

f:id:u874072e:20200116155731p:plain
プロセスの起動画面

このメッセージを確認した後に、
Unity上の再生ボタンをクリックします。

正しく実行できていれば、
自動的にボールが転がり始めます。

最初のうちは全然ターゲットに向かいませんが、
しばらくすると、ターゲットに向かって動くようになり、
学習が進んでいる様子がわかります。

おまけ:学習状況をtensorboardで確認する

ターミナル上にも学習ログが表示されますが、
tensorboardを使うとグラフィカルに学習状況が確認できます。

tensorboardを使うには、
ターミナル上で次のコマンドを実行します。

tensorboard --logdir summaries

summariesは学習結果が保存されているフォルダです。

コマンドを実行後、ブラウザから
http://localhost:6006/ にアクセスすると、
下のような画面が表示され、
学習状況の確認ができます。

f:id:u874072e:20200116161340p:plain
TensorBoardの画面

学習結果の活用

強化学習によって、
現状の入力と行動への出力の対応関係が学習されます。
この学習の結果をエージェントに設定すれば、
エージェントが自分で行動を選択することができます。

学習結果はmodelsフォルダに保存されています。
models/test-1の中のMy Behavior.nnファイルを
Unityのアセットフォルダに移しておきましょう。
※ファイルの名前は、Behavior Parameterで設定したBehavior Nameで決まります。

このファイルをエージェントのBehavior ParameterのModelに設定します。

f:id:u874072e:20200116162001p:plain:w300
学習済みモデルの設定

これで実行してみると下の動画のように、
上手に追いかけてくれました。


箱を追いかけるボール

まとめ

ML-Agentsを使って強化学習を行う流れを、
チュートリアルに従って実行してみました。

準備がかなり多めなので、
初見の敷居が高そうですが、
エージェントの記述はかなりシンプルなので使いやすそうです。

もう少し手順が簡略化されて、
スクリプトやオブジェクトの配置に集中できるようになるとさらに便利ですね。

WordPress のサイトが別のサイトに飛ばされるハッキングを受けた時に自力復旧でやったこと

年明が明けて間も無く、
WordPressで運営している自分のサイトにアクセスすると、
「ロボットでない場合は許可をクリックします」
と、通知許可を要求してくる
謎のサイトにリダイレクトされるようになりました。

アクセスのほとんどないサイトなので、
狙われることもないだろうと油断していましたが、
まんまとやられました。

とりあえず、ハッキングされたサイトへのアクセスを遮断して、
他に迷惑のかからないようにしたところで、
自力復旧に取り掛かりました。

ハッキングされないに越したことはないですが、
また同じようなことがあった時に役立つように、
対処した方法をまとめておきます。

アクセスの遮断

アクセスがほとんどないサイトではありますが、
アクセス頂いたユーザーが不正なページに飛ばされるのを避けるため、
真っ先に行ったのがサイトへのアクセスの遮断です。

.htaccess」ファイルを編集して、
サイトへのアクセスを、
復旧中の仮ページにリダイレクトしました。

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{http_host} ^[ハックされたドメイン名]
RewriteRule ^(.*) [仮ページのURL] [R=301,L]
</IfModule>

復旧が終わったら内容を元に戻すことになるので、
バックアップを忘れないようにしておきます。

ただし、ドメイン名でリダイレクトを設定しているので、
IPアドレス直打ちでアクセスすることはできる状態です。
完全な遮断ではないことに注意が必要ではありますが、
逆に、IPアドレスでアクセスすれば復旧状況をリアルタイムで確認できる
苦肉の策でもあります。

リダイレクト先を調べる

どこにリダイレクトされているか分かれば、
復旧すべき箇所を探す手がかりになります。

とはいえ、あっという間に複数のサイトを経由して、
リダイレクトされてしまうので、
自分のサイトから飛んだのはどこのサイトかが追いづらくなります。

そこで便利なのが、ブラウザの開発者ツールです。
ここでは、Chromeを使ってリダイレクトを辿ることにします。

Chrome上で、Ctrl+Shift+c(MacはCommand+Shift+c)を入力すると、
開発者ツールが開きます。

続いて「Network」タブを開き、
「Preserve log」にチェックを入れます。
また、左端の丸印が赤丸になっていることを確認します。 これで、どのようなコンテンツを
どこのサイトから取ってくるかが記録されます。

f:id:u874072e:20200108151951p:plain
開発者ツール

このままハッキングされたサイトにアクセスすれば、
開発者ツール上に、読み込まれたコンテンツ一覧が表示され、
それぞれのコンテンツをクリックすれば、
そのコンテンツがどこから読み込まれたかがわかります。

例えば、google analyticsスクリプトファイルは下のように表示されます。

f:id:u874072e:20200108152631p:plain:h300
ヘッダ情報

Request URLが読み込んだサイトを表していて、
基本的には、自分のサイトそのものですが、
上の例のように自分で読み込むようにしたサイトも含まれます。

ここのURLに見覚えがないものがないかを探します。
自分の場合は、statistic.admarketlocation.comというサイトに、
リダイレクトされていることが分かりました。

リダイレクトの原因を探す

リダイレクト先がわかったので、
これを手がかりとして、
どこにリダイレクトが埋め込まれているかを探していきます。

ファイル内を全探索

ファイルの中身が書き換えられている可能性を考えて、
ワードプレスフォルダ内の全ファイルの中から、
リダイレクト先の文字列が含まれていないかを検索します。

この際に便利なのは、grepコマンドでしょう。

grep [検索文字列] -r [フォルダ名]

フォルダの階層を再帰的に探索してくれます。

私の場合は、ファイルにリダイレクトは埋め込まれていなかったため、
この操作では何も見つかりませんでした。

データベースを検索

WordPressは記事や設定値をデータベースで管理しているので、
データベースの中も探索する必要があります。

私の環境では、mySQLを用いているので、

mysql -u [ユーザー名] -p

でデータベースにログイン後、

use [WordPressのデータベース名];

でデータベースの操作を開始します。

wp_optionsの検索

まずは、様々な設定値が管理されているwp_optionsを検索してみます。

select * from wp_options where option_value
  like "%リダイレクト先%";

like検索で部分一致検索を行い、
リダイレクト先のURLが含まれていないかを確認します。

すると、

f:id:u874072e:20200108155804p:plain
wp_optioinsの検索結果

見つかりました。
siteurlhomeが別のサイトのURLに書き換えられています。

早速直しておきましょう。

update wp_optoins set option_value="[正しいURL]" 
  where option_name="siteurl" or option_name="home";

wp_postsの検索

次に、記事の中に埋め込まれている可能性を考えます。
wp_optiionsの場合と同様に、

select * from wp_posts where post_content
  like "%リダイレクト先%";

で、記事の中身に検索をかけると、

f:id:u874072e:20200108160404p:plain
wp_postsの検索結果

wp-simple-plugin.phpというタイトルで、
リンク先のアドレスが記入された記事が見つかりました。

これらの記事は勝手に作られたものなので、
全部削除することにします。

delete from wp_posts where post_title="wp-simple-plugin.php";

とりあえず、これでリダイレクト自体は解消されました。
管理画面も復活です。

ウイルスチェックする

ハックされたということは、
バックドアが仕掛けられている可能性を考える必要があります。

復活した管理画面から、
セキュリティ用のプラグインを導入して
スキャンをかけるのが手っ取り早そうです。
ついでに、再発の防止にもなるかもしれません。

今回は、「Wordfence」というプラグインをインストールしました。
プラグインを有効にすると、
管理画面のメニューに「Wordfence」が表示されるので、
「Wordfence」→「Scan」と移動して、
「START NEW SCAN」をクリックします。

f:id:u874072e:20200108163519p:plain
スキャン開始

スキャンが終了するまで暫く待つと、
「wp-simple-plugin.php」というどこかで見たファイルが、
悪意のあるファイルとして見つかりました。

f:id:u874072e:20200108162709p:plain
スキャン結果

日付も2020/01ですし、間違いなさそうですね。
ということで、ファイルを削除しておきます。

パスワードを変更する

最後に、WordPressのアカウントのパスワードを変更しておきます。

管理画面のメニューから
「Users」→「Your Profile」を選び、
「Account Management」の「Generate Password」をクリックして、
新しいパスワードを設定した後、
「Update Profile」をクリックします。

f:id:u874072e:20200108164407p:plain
パスワードの変更

まとめ

新年早々からハッキングに遭うとは付いてないですね。

色々試行錯誤しているうちに、
WordPressにより詳しくなれた気がするのはよかったですが、
できれば再発しないようにセキュリティ対策を
普段から実践していかないとですね。

"Just my two cents"とは

英語のビジネスメールでこんなフレーズを見かけることがあります。

"Just my two cents"

直訳すると、
「私の2セント」
ですが、これでは意味不明ですね。

このフレーズは、自分の意見を控えめに述べるときに、
その前置き(あるいは結び)として使うフレーズです。

あえて日本語に訳すなら、
「単なる私の思いつき」とか、
「愚見を申しますと」みたいな形ですね。

自分の意見を卑下する表現

このフレーズで、何が2セントなのでしょうか。

それは、その前後の文章、つまり自分の述べた意見が、
2セントの価値であるということです。

2セントは日本円にして2~3円程度で、
価値が小さい意見であるといって、
自分が述べる意見を卑下する表現になっています。

このため、自分の意見を控えめに述べる時に、
"my two cents"という表現がよく用いられます。

相手にも使える

2セントの価値の意見であるというと、
"your two cents"のように相手に使うと失礼な気がしますが、
実は、この表現も使われます。

例えば、相手の意見を聞かせてほしい場合に、
"Put in your two cents." と言うことがあります。

相手の意見を2セントの価値しかないと言うのは気が引けますが、
"two cents"の由来を見てみると納得できるでしょう。

two centsの由来

自分の意見の価値を小さく言うだけなら、
"one cent"でも、"three cents"でも良さそうな気がしますが、
なぜ"two cents"なのでしょうか。

"two cents"の由来にはいくつか説がありますが、
昔、手紙を送るための費用が2セント(2ペニー)だったから
という説があります。

意見を相手に伝えるための費用が2セントだから、
"my two cents", "your two cents"が、
私の意見、あなたの意見を表すということです。
これなら、"your two cents"にも抵抗がなくなりますね。

バリエーション

"Just my two cents"には、
いくつか書き方にバリエーションがあります。

Justは省略して、"my two cents"としたり、
2セントの価値を表すために、
"my two cents's worth"と表現することがあります。

また、2セントの表記として、
"my 2¢"
"my 0.02$"
のような書き方がされる場合もあります。

まとめ

初見ではわかりにくいのですが、
意見を控えめに述べる時に便利な
"just my two cents"というフレーズを紹介しました。

補色のスペクトル的な意味

補色とは、
色相環の反対側にある色」、
「混ぜると無彩色になる色」
などの特徴で説明されることが多いのですが、
その物理的な性質はどのようなものでしょうか。

ここでは、補色の物理的な意味を、
光のスペクトルとの関係から紐解いていきましょう。

色と光

私たちの眼は光の刺激を受け取ることで、
目の前の風景をイメージとして認識します。

光は波の性質を持っていて、
様々な波長の光がありますが、
私たちは、その波長の違いを色として認識しています。

例えば、りんごが赤色に見えるのは、
様々な波長の光が含まれた光源の光が、
りんごの表面で吸収されて、
吸収されずに残った赤い光が反射されて目に届くことで、
私たちが赤色を認識しているからです。

f:id:u874072e:20191217154434p:plain
色が見える仕組み

重要なのは、ここで赤は吸収されずに反射された色で、
それ以外はりんごが吸収しているということです。

絵の具を混ぜる(減法混色)

補色の代表的な性質として、
「混ぜると無彩色になる」というものがあります。
言い換えると、色が消える、モノクロになるということです。

色を混ぜる時には何が起こっているのでしょうか。
赤の絵の具は先ほどのリンゴと同じように、
赤色以外の光を吸収してしまう物質です。
また、緑の絵の具は、緑色以外の光を吸収してしまう物質です。
これらを混ぜると、赤の絵の具は赤以外の光を、
緑の絵の具は緑以外の光を吸収し、
吸収されずに残った光が目に見えることいなります。

f:id:u874072e:20191217161723p:plain
補色の混色

実は、赤と緑は補色の関係にあり、
ほぼ全ての波長が赤か緑の絵の具に吸収されて
目に見えるのは黒っぽい色になります。

このように、絵の具の色を混ぜるという操作は、
それぞれの絵の具が色を吸収しあって、
吸収されなかった色の光が目に届くようになることを表します。

絵の具を混ぜるほど、吸収される光の波長が増えていき、
反射される光が減るので、どんどん暗い色になります。
このように混ぜると色が引き算されていくので、
減法混色とも呼ばれます。

補色のスペクトル

それでは、
この補色はスペクトル的にはどういう色だと言えるでしょうか。

2つの色を混ぜた時に、色がなくなるには、
他の色が吸収しきれなかった光を、
別の色が吸収すれば良いことに気がつきます。

すると、混ぜて無彩色になる色、つまり補色は、
反射される色と吸収される色を丁度反対にした色と言えます。

下の図では、それぞれの色について、
波長に分解した時の波長の強さをグラフで表現したイメージ図です。

f:id:u874072e:20191217155704p:plain:w400
補色のスペクトルのイメージ

赤の補色である緑は、
赤で反射される波長の光が吸収されるので、
丁度、グラフを上下逆さまにしたようになります。
これが補色のスペクトルです。

ここで、次のような疑問を持たれるかもしれません。
「緑色は、緑色の波長にピークがくる山のようなグラフで表せるのに、
 上の図では赤色以外の青や紫など、
 緑とは違う色も沢山含んだ色になっている。
 これって本当に緑色なの?」

実は人間の目には、違うスペクトルでも同じ色に見えるという
条件等色と呼ばれる現象が生じます。
このため、緑にピークのあるスペクトルも、
上の図のようなスペクトルも緑なのです。
特に、空の青には紫色の光も多く含まれていますが、
この条件等色によって青色に見えています。

clean-copy-of-onenote.hatenablog.com

補色の生成

補色はスペクトルを上下反転したものであると説明しました。
そこで、実際にスペクトルを反転して補色を作ってみましょう。

コードはRで記述しています。

波長と色の対応

補色を作るためには、
波長と色がどのように対応しているかを与える必要があります。

ここでは、波長に応じた色を求める関数lambda2colorをお借りしました。

スペクトルの分解粒度

スペクトルで分解する波長の数と、
波長の代表点を定めておきます。

ここでは、380nmから770nmまでの波長を
100分割して、スペクトルを作成します。

Ncol=100
lambdas = seq(from=380,to=770,length.out=Ncol)

単色スペクトルの生成

赤や青といった一つ一つの色の光を表現するためのスペクトルを、
以下の関数で生成します。

norm_col_weight = function(lambda){
  x = dnorm(lambdas,mean=lambda,sd = 60)
  return(x / max(x))
}

ただし、光の強度は複数の波長にまたがっているものとして、
中心の波長をピークとした正規分布でスペクトルを生成しています。

例えば、700nmをピークとした赤色に対しては、
下のようなスペクトルが生成されます。

f:id:u874072e:20191219115449p:plain:w300
赤のスペクトル

スペクトルの反転

補色はスペクトルを反転することで計算します。
下の関数は、波長毎の強度の強弱を反転する関数です。

compl_color = function(weights){
  mix_colors(lambda2color(lambdas),1 - weights)
}

混色(加法混色)

スペクトルの反転で、
補色をスペクトルに分解した時の、
各波長(色)の強度が計算されます。
あとは、それぞれの強度に従って色を混ぜる必要があります。

光の混色は、絵の具の混色とは違い、
混ぜれば混ぜるほど明るくなる加法混色と呼ばれます。

下の関数は、16進数で表現された色を、
weightsで指定した割合で加法混色する関数です。
normは重みの和を1になるようにリスケールするか否かを表します。

mix_colors = function(cols,weights,norm=T){
  to_rgb = function(x){
    x = substr(x,2,9)
    x = as.numeric(paste("0x",x,sep=""))
    
    r1 = as.numeric(paste("0x010000"))
    r = x %/% r1
    x = x %% r1
    
    g1 = as.numeric(paste("0x000100"))
    g = x %/% g1
    x = x %% g1
    
    b = x
    
    return(c(r=r,g=g,b=b))
  }
  
 if(norm){weights = weights / sum(weights)}
  
  rgb_cols = sapply(cols,to_rgb)
  mix_rgb = round(rgb_cols %*% cbind(weights))
  mix_rgb = sapply(mix_rgb,function(x){min(255,x)})
  print(mix_rgb)
  paste0("#", to_hex(mix_rgb[1]), to_hex(mix_rgb[2]), to_hex(mix_rgb[3]))
}

補色の計算例

上の関数を使って、補色を計算してみます。

weight1 = norm_col_weight(700)
col1 = mix_colors(lambda2color(lambdas),weight1)
col2 = compl_color(weight1)

plot(NULL, xlim=c(0,2), ylim=c(1,2),
     axes=FALSE, xlab="", ylab="")
rect(0:1, 1.1, 1:2, 2,
     col=c(col1,col2))

まず、norm_col_weight(700)'で700nmを中心とした、 単色のスペクトルを生成し、 スペクトルに沿って色を混ぜ合わせてcol1`を生成しています。

次に、compl_colorcol1と補色の関係にあるcol2を生成しています。

最後にそれぞれの色をプロットしています。

下の図は、もとの色の波長を変えながら補色を計算した結果です。

f:id:u874072e:20191219113345p:plain:w300
計算された補色

  • 赤-青緑
  • 黄緑-紫
  • 青-黄土色
  • 黄色-青紫

のように、補色が生成されていることがわかります。

まとめ

ここでは、色の補色について、
そのスペクトル的な意味を説明しました。

また、実際にプログラムで補色を生成してみました。

実は補色にも種類があり、
ここで紹介したのはRYB補色と呼ばれるものです。
この補色では、赤と緑が補色関係になります。
美術の教科書などではこの補色を学ぶことが多いように思います。

一方で、赤とシアン(水色)が補色関係になる
RGB補色と呼ばれるものもあり、
こちらの補色は、PCの色の表現的に簡単に計算できるので、
具体的な計算方法を紹介しているサイトは数多く見つかります。

2019年ブログ改善で効果のあったものまとめ

今年は、ブログのデザイン面を何度かカスタマイズして、
改善に取り組んできました。

下の記事で紹介している通り、
それぞれの改善の効果を自動で継続的にモニタリングしているので、
効果のあったものと効果のなかったものが一目瞭然です。

clean-copy-of-onenote.hatenablog.com

そこで、改善効果のあったものをここでまとめておきたいと思います。
と言っても、数はあまり多くありませんが。

記事内への広告自動挿入

手動で全ての記事に広告を挿入するのは、
非常に面倒なので、
指定したタグの前後に自動的に広告を挿入するようにしました。

clean-copy-of-onenote.hatenablog.com

これで、広告の貼り忘れがなくなり、
記事の途中で広告が目に入りやすくなります。

結果として、
広告クリック率が2.5倍になりました。

サイドバー広告を追尾広告に

Google Adsenseで追尾広告の解禁を受けて、
サイドバーに追尾広告を設置しました。

clean-copy-of-onenote.hatenablog.com

サイドバーにはプロフィールや関連記事を配置していますが、
長い記事になると、記事の後ろになるにつれて、
サイドバーのコンテンツがなくなり、
横に空白のスペースができていました。

そこに、追尾広告を設置することで、
空白の無駄スペースを有効利用できます。

結果として、
広告クリック率が1.7倍になりました。

おまけ:効果のなかったもの

もちろん、効果のあったものばかりではありません。
改善しようと思って行ったものの中に、
予想していた効果を生まなかったものもあります。

以下では、思ったような効果が出なかったものを紹介します。

メニューバーの設置

このブログでは、
以前から回遊率が低く、
ユーザー一人が見るページは、
ほぼ1ページ限りでした。

せっかくアクセスして頂いているなら、
ブログ内を色々回ってみてもらいたいということで、
ページ上部にメニューバーを設置して、
「このブログにはこんなコンテンツがありますよ」
というアピールをしてみました。

が、結果としては、
回遊率は変わりませんでした。

このブログのアクセスが多い記事には、
問題解決型が多いので、
そもそも、目的以外のページにアクセスする人は少ないのですね。

サイドバーの記事を目立つように

上と同じく回遊率を向上させる目的で、
サイドバーに表示される記事を目立つように

  • 順位の表示
  • サムネの追加

など、見た目のカスタマイズを行いました。

clean-copy-of-onenote.hatenablog.com

が、これも結果として、
回遊率は変わりませんでした。

やはり、このブログでは、
単体の記事をみてもらうというスタイルから抜けるのは難しそうです。

まとめ

今年の行ったブログ改善のうち効果のあったものを紹介しました。
広告系では改善効果が大きくあったものの、
デザインやサイトの導線作りには失敗したという感じですね。

テーマを絞らない雑記型では回遊率をあげるのは難しそうです。

Unity で万有引力を実装してみる

Unityではデフォルトで重力による運動をシミュレートすることができますが、
あくまで地面に向かって一方向的かつ一定の大きさの力に限定されています。

惑星の動きなどをシミュレーションするには、
惑星の位置関係によって、
重力の働く向きや大きさが変わる状況を、
シミュレーションする必要があります。

万有引力の実装は検索すればいくつもヒットするのですが、
引力が働く物体をスクリプト内で明示的に指定するものばかりでした。
これだと、複数の物体間の万有引力を記述するのが面倒そうです。

Unityではコンポーネントを追加することで物体の動き方を決める
というのが自然だと思うので、
万有引力コンポーネント」を追加すれば、
その物体同士が自然に引き合うような形が理想的にでしょう。

ということで、ここでは、「万有引力コンポーネントを実装して、
引き合いたいもの同士にコンポーネントの追加のみで、
万有引力が働くようにしてみましょう。


万有引力と三体問題

考え方

万有引力は、質量を持った全ての物体が互いに引き合う現象です。

一つの物体に注目すれば、
その物体は他の全ての物体から引っ張られることになります。

ただし、Unityの中のオブジェクトには、
光源やカメラなどもあり、
これらも含めたオブジェクト全てから引っ張られるというのはよろしくないでしょう。

ということで、「万有引力コンポーネント」を持ったオブジェクトのみが、
同じく「万有引力コンポーネント」を持ったオブジェクトから引っ張られる
という動作を実装するのが良さそうです。

この時、万有引力が働くオブジェクトと、
そうでないオブジェクトを識別する必要があります。
ここでは、Unityのタグを使ってこの識別を行うことにします。

準備

タグを使って、万有引力が働く物体を識別するため、
万有引力用のタグを作成しておきます。

Unity上で適当なオブジェクトを選択して、
そのInspectorウィンドウから、
「Tag」→「Add Tag」を選択します。

f:id:u874072e:20191213130759p:plain
タグの追加

続いて、Tagsの「+」ボタンをクリックして、
タグの名前を入力して、「Save」をクリックします。
ここでは「ug_obj」をタグの名前としています。

f:id:u874072e:20191213131434p:plain
ug_objタグを追加

スクリプト

下のように万有引力コンポーネントの中身を作ります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class universal_gravity : MonoBehaviour
{
    Rigidbody rb;
    float CONST_G = 0.0001f;
    string TAG_NAME = "ug_obj";
    // Start is called before the first frame update
    void Start()
    {
        gameObject.AddComponent<Rigidbody>();
        rb = gameObject.GetComponent<Rigidbody>();
        rb.useGravity = false;

        gameObject.tag = TAG_NAME;
    }

    // Update is called once per frame
    void Update()
    {
        var objs = GameObject.FindGameObjectsWithTag(TAG_NAME);

        var p0 = rb.position;

        foreach(var obj in objs)
        {
            var trb = obj.GetComponent<Rigidbody>();
            var q0 = trb.position;
            var pmq = p0 - q0;

            var F = - CONST_G * rb.mass * trb.mass * pmq * Mathf.Pow(pmq.magnitude, 3);
            rb.AddForce(F, ForceMode.Impulse);
        }
    }
}

以降ではスクリプトの詳細を説明します。

定数

CONST_G

万有引力定数を表します。
万有引力の強さを決める数値ですが、
現実の万有引力はこの定数が小さいため極めて小さな力になります。
必要に応じて調整しましょう。

TAG_NAME

万有引力が働くオブジェクトを識別するためのタグです。
準備で用意したタグの名前を指定します。

Start処理

Start関数の中では、

  • Rigidbody の追加
  • Rigidbodyの重力の無効化
  • オブジェクトへのタグの追加

を行なっています。

質量を持った剛体としての動きをシミュレーションするために、
Rigidbodyを持っていない場合はRigidbodyを追加し、
万有引力を別途実装するので、
Rigidbodyに付随する重力は無効にしています。

Update処理

Update関数では、
タグを目印にオブジェクトをサーチして、
他のオブジェクトから受ける万有引力を、
次の式で計算して加えています。

 \vec{F} = - G \frac{Mm}{r^3}\vec{r}

G万有引力定数、M,mは二つの物体の質量、
\vec{r}は物体の相対的な位置ベクトルを表します。

ベクトル式ですが、
コードの中でも数式と同じように一行で記述できているのが、
気持ちいいですよね。

var F = - CONST_G * rb.mass * trb.mass * pmq * Mathf.Pow(pmq.magnitude, 3);

ただし、オブジェクトが増えて計算が重くなる場合には、
近い距離のオブジェクトだけ万有引力が働くなどの近似を行うなど
工夫が必要そうです。

使い方

準備でタグを生成したら、
万有引力を働かせたいオブジェクトに、
上記の万有引力コンポーネントを追加するだけです。

下の動画は、万有引力コンポーネントを持った二つの物体に、
適当な初速を与えて動きをシミュレーションしています。
また、途中で3つ目の物体を追加しています。


万有引力と三体問題

まとめ

Unity で万有引力コンポーネントを実装して、
コンポーネントを追加するだけで、
オブジェクト同士に引力が働く動作を実装しました。

引力が働きあうオブジェクトを識別するために、
タグを使用しましたが、
タグは1つのオブジェクトに1つしかつけられないので、
他に良さそうな方法があれば、そちらを使いたいですね。

と思ったら、タグではなくて、
クラスのインスタンスリストを保持すれば良いことに気づいたので、
下のスクリプトを使えばタグ不要です。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class universal_gravity : MonoBehaviour
{
    Rigidbody rb;
    float CONST_G = 0.0001f;
//    string TAG_NAME = "ug_obj";
    static List<universal_gravity> ug_obs = new List<universal_gravity>();
    // Start is called before the first frame update
    void Start()
    {
        gameObject.AddComponent<Rigidbody>();
        rb = gameObject.GetComponent<Rigidbody>();
        rb.useGravity = false;

//       gameObject.tag = TAG_NAME;

        ug_obs.Add(this);
    }

    // Update is called once per frame
    void Update()
    {
        //var objs = GameObject.FindGameObjectsWithTag(TAG_NAME);

        var p0 = rb.position;

        foreach(var obj in ug_obs)
        {
            var trb = obj.GetComponent<Rigidbody>();
            var q0 = trb.position;
            var pmq = p0 - q0;

            var F = - CONST_G * rb.mass * trb.mass * pmq * Mathf.Pow(pmq.magnitude, 3);
            rb.AddForce(F, ForceMode.Impulse);
        }
    }
}
プライバシーポリシー