CourseraのMachine Learningから線形回帰を学んだのでまとめてみた
CourseraのMachine Learningを受講しています。時間を見つけてはコツコツ進めて今のところWeek4に差し掛かったところです。Week4ではNeural Networksの話に入り一段とレベルが高くなった印象を受けています。Week1からWeek3までに学んだことを復習する必要があるなと焦りが生まれました。
受講中は配布される資料や動画を見たり他の日本人の方のブログを拝見したりと課題に取り組んできました。このタイミングで復習して整理することでWeek4以降の学習が快適になるのではないかと淡い期待を込めてWeek1からまとめてみます。
Machine Learning - Stanford University | Coursera
こちらが受講しているMachine Learningのコースです。MOOCは好きな時間に進められるし前編英語(動画は日本語字幕あり)なので英語の学習になってオススメです。
どこまでの内容をまとめるか
このエントリではWeek1の内容に触れています。
線形回帰とは?
ある土地の家の価格とその家の部屋数の相関をグラフで表すと以下のようになったとします。
Xが部屋数(RM)でYが価格(MEDV)です。
(出典:Housing Data Set)
※ このデータは部屋数以外に、その土地の犯罪率だったり児童と教師の比率など複数の要素から構成されています。
部屋数が4つの場合や部屋数が7つの場合はグラフから予想できそうです。 このとき頭の中ではグラフに右肩上がりな直線をイメージして予想できますが、この直線を数式から導き出すことを学びました。 この導き方は統計学では回帰分析の一種として線形回帰と呼ばれています。
仮定関数と目的関数
部屋数をX、部屋の価格をYとすると相関を表すグラフを引くための1次関数の式は次のようになります。(懐かしい数式)
Machine Learningのコースではθをつかって次のような式で定義しています。
は仮定関数と呼ばれます。家の価格予想に最適な直線を引くために との数値を変えながらグラフにフィットした仮定関数を導き出します。
つまり右肩上がりの直線のグラフを引くために最適な の a
とb
を決めるということですね。
仮定関数がフィットしているか計算するための関数である目的関数があります。
この目的関数をつかってを最小にする とを導き出します。
最急降下法
目的関数を最小にする方法に最急降下法があります。
ここで微分が出てきます。微分は傾きを求めますがこのアルゴリズムを使い傾きが最小になるまで学習を繰り返していきます。傾きの値が最小になるほどグラフにフィットしたが求められます。 は学習率と呼ばれ数値が大きいほど傾きの変動幅が大きくなりフィットしたデータが得られず、小さいほど学習はゆっくりと進み確実にデータにフィットした値が求められます。
Octaveでプログラム化する
コースの課題ではOctaveを使いプログラミング課題を提出します。 最初に示した家の価格と部屋数のグラフのデータを使い、更にこれまでのアルゴリズムからデータにフィットした直線をグラフにプロットしてみます。
目的関数
function J = computeCost(X, y, theta) m = length(y); J = sum((X*theta -y).^2) / (2* m); end
function [theta, J_history] = gradientDescent(X, y, theta, alpha, num_iters) m = length(y); J_history = zeros(num_iters, 1); for iter = 1:num_iters theta = theta - alpha / m * X' * (X * theta -y); J_history(iter) = computeCost(X, y, theta); end end
目的関数を求めグラフにプロットする。そして部屋数が5つのときの価格を予想する。
%% Initialization clear ; close all; clc data = load("housing.txt"); x = data(:, 6); y = data(:, 14); m = length(y); theta = zeros(2, 1); X = [ones(m, 1), x] %% Compute Cost J = computeCost(X, y, theta) %% Gradient Descent iterations = 1500; alpha = 0.01; [theta J_history] = gradientDescent(X, y, theta, alpha, iterations); %% Output fprintf('Initial cost = %f\n', J); fprintf('Final cost = %f\n', J_history(iterations)); fprintf('Theta found by gradient descent: '); fprintf('%f %f \n', theta(1), theta(2)); %% Plot data figure; hold on; plot(x, y, 'r+', 'LineWidth', 2); plot(X(:,2), X*theta, '-') xlabel('RM'); ylabel('MEDV'); %% Predict fprintf('for RM = 5, MEDV = %f\n', [1, 5] *theta);
出力したグラフ
データにフィットした直線がプロットできたようです。
出力した数値
J = 296.07 initial cost 296.073458 final cost 27.131052 Theta found by gradient descent: -5.254087 4.477681 for RM = 5, MEDV = 17.134317
部屋数が5つのときに 17.134317
と予想できました。グラフにプロットされたデータとフィットされているようです。
まとめ
- 最後に出したグラフですが、もう少し角度が急な直線のほうがグラフにフィットしているようです。プロットして上手く行かなければ調整して再度プロットして、の繰り返しが機械学習の大事な工程なのでしょう。
- 線形回帰はこの先で学ぶ機械学習の知識のベースになっています。データを分類する場合などに流用できます。
- 今回の学習データは
部屋数
の1つでしたが部屋数に加え犯罪率
、児童と教師の比率
などの複数の要素からも線形回帰をベースに家の価格を予想できる。(重回帰分析)この内容はWeek2で学びました。 - MOOCで学び、コースを修了した先輩たちのブログで学び、自分のブログでアウトプットする、など多角的に学ぶことが大事。(ここに
書籍を読んで学ぶ
も入れたい:money_with_wings: ) - 数式書くの大変。
Page Object PatternをベースにTestCafeでE2Eテストを作ってみた
以前のエントリでKotlinでSelenideを使ったE2Eテストを作ったときもPage Object Patternを利用して見通しの良いテストコードが書けました。TestCafeでも同様にPage Object Patternを利用することが推奨されています。
今回はTestCafeでもPage Object Patternを利用してテストを書いてみました。
何をテストするか
次のサイトのログイン認証をテストします。
テスト内容としては次のとおりです。
- 認証画面のURLを開く
- IDとパスワードを入力する
- ログインボタンをクリックする
- ログインが完了するとHome画面にリダイレクトされるので正しくリダイレクトされているか
- ログインが完了するとヘッダーモジュールにアカウント名が表示されてるので正しく表示されているか
テストコードのフォルダ構成
プロジェクトのフォルダ構成は次のように役割ごとに整理しました。
tests ├── features ├── operators ├── pages └── support
- featuresにはスペックコードをまとめます。
- operatorsにはPageオブジェクトから画面要素を参照してログインボタンをクリックするなどのオペレーションをまとめます。
- pagesには画面の要素を参照できるようなPageオブジェクトを画面ごとにまとめます。今回は認証画面とHome画面をPageクラスにしました。
- supportにはスペックコードでテストを進めるときに画面キャプチャを撮るなどのユーティリティ機能をまとめます。
次からはTestCafeのAPIを利用して記述したコード例を紹介します。
Pageクラス
TestCafeのAPIを利用して認証画面のPageクラスを作ります。
import { Selector } from 'testcafe'; export default class AuthPage { constructor () { this.url = 'https://freshlive.tv/auth/fresh_id'; this.idInput = Selector('#user_id'); this.pwInput = Selector('#password'); this.submitBtn = Selector('button[type=submit]'); } }
- 認証画面のURLを変数に定義する。
- テストに必要な画面要素はSelectorを利用して定義する。
- idInputはログインID、pwInputはパスワードの入力フィールドです。
- submitBtnはログインボタンです。
これで認証画面の要素をまとめたAuthPageクラスができあがりました。
Operatorクラス
TestCafeのAPIを利用してOperatorクラスを作ります。
今回は認証画面でログイン認証する必要があるのでログイン操作をまとめます。
import {t} from 'testcafe'; import AuthPage from '../../pages/auth/auth-page.js'; export default class AuthOperator { constructor () { this.page = new AuthPage(); } async open() { await t .navigateTo(this.page.url) } async authorize(id, password) { await this.open(); await t .typeText(this.page.idInput, id) .typeText(this.page.pwInput, password) .click(this.page.submitBtn) } }
- constructor ()でAuthPageクラスからPageオブジェクトを生成します。
- authorize(id, password)では認証画面を開きIDの入力とパスワードの入力を行い最後にログインボタンをクリックしています。typeTextとclickで入力とクリックの操作ができます。
- tはTestCafeのTestConrollerオブジェクトです。async-awaitとimportをすることでテスト実行時のTestConrollerオブジェクトと同期できます。0.13.0のリリース(Using test controller outside of test code (#1166))でTestConrollerをテストコード外でも参照することができるようになりました。(いいね!)
Supportクラス
TestCafeのAPIを利用してのSupportクラスを作ります。
今回は画面キャプチャを撮るユーティリティクラスを作りました。
import {t} from 'testcafe'; export default class ScreenshotSupport { constructor () { } async take() { await t .takeScreenshot; } }
Specクラス
TestCafeのAPIを利用してのSpecクラスを作ります。
このクラスでテスト内容をまとめていきます。
import { expect } from 'chai'; import HomePage from '../../pages/home/home-page.js'; import AuthOperator from '../../operators/auth/auth-operator.js'; import ScreenshotSupport from '../../support/screenshot-support.js'; const homePage = new HomePage(); const authOperator = new AuthOperator(); const screenshot = new ScreenshotSupport(); const userId = ' xxxxxxxx'; const password = 'xxxxxxxx'; const accountId = 'My Account'; fixture `auth fixtures`; test('authorized at page then is appeared your account name on element of header.', async t => { // ログインする await authOperator.authorize(userId, password); // スクリーンショット撮る await screenshot.take(); // ログイン後にHomeにリダイレクトをするのでURLが正しく切り替わっているか const docURI = await t.eval(() => document.documentURI); expect(docURI).eql(`${homePage.url}?&login_succeeded=true`); // ヘッダーメニューにログインしたアカウント名が表示されているか await t .expect(homePage.accountMenuDropdown.exists).eql(true) .expect(homePage.accountName.exists).eql(true) .expect(homePage.accountName.innerText).eql(accountId); });
- fixtureはテストをカテゴライズする機能でテスト実行時にfixtures単位でテストを実行するオプションがあります。
- testではテストのタイトルとテストコードを記述していきます。
- Operatorクラスでまとめた認証画面でログインするauthorize(userId, password)を呼びだしてログインを実行しています。
- ログイン後にURLが切り替わっているかdocURI変数にURLを入れてチェックしています。
- 最後にexpectを利用してヘッダーモジュールにアカウント名が表示されているかをチェックしています。
まとめ
ソースを公開しています
関連エントリ
WebDriver不要のTestCafeを使ったE2EテストをChatOpsに導入してみた
今回はE2EテストをつくれるTestCafeをつかってみたエントリです。
以前のエントリではSelenideをつかったE2Eテストの紹介をしました。Selenideも特徴がありますがTestCafeも抜群の特徴があります。
TestCafeではテストコードはNodeで書いていきます。そのため広くエンジニアメンバーがE2Eテストを書けるメリットがあります。ES2016 using をつかってテストコードが書かれているためasync/awaitなどの特徴的な構文も使えます。
最大の特徴はテストをリモートで実行できること
TestCafeのプリインストールでテストするブラウザとしてchromeとsafariが用意されています。ブラウザを指定したテストの実行は次のようになります。
実行するとchromeとsafariが立ち上がり`tests/test.js`のテストが実行されます。
この方法は予めブラウザを指定する方法ですが テストを実行するURLを生成するremoteというオプションがあります。
実行は次のようになります。
remote:1としている箇所がリモートで確認するURLの生成数を指定しています。TestCafeは指定されたURL数を生成して返します。テスターはURLをコピーして好きなブラウザにアクセスしてテストを実行できます。ブラウザにアクセスした後の実行結果は次のようになります。
TestCafeは自身でテスト用のブラウザを持たずユニークなURLを生成してテスターにブラウザを選ばせることで多種多様なブラウザのテストを可能にします。
このリモート機能を活用してE2EテストをChatOpsに組み込むアイデアを考え実装してみました。
TestCafeのRemoteConnetionをChatOpsに導入する
E2Eテストの運用にChatOpsを導入します。テスターはslackからBot経由でテスト開始を通知してBotとTestCafeが連携します。
それぞれ図にすると次のようになります。
リモートURLをslackで受け取るまで
- テスターはslackに@bot test webを送信する。
- Botはイベントを受け取りTestCafeにリモートURLの生成依頼を通知する。
- TestCafe(hapi)は生成依頼を受け取りリモートURLを生成してBotへ返す。
- BotはリモートURLをルームに送信する。
こんな感じの流れで補足として次のような内容があります。
- docker-composeでHubotとTestCafeのサビースをつくりコンテナ化した。
- HubotとTestCafeはHTTP通信でリモートURLのやり取りを行う。
- TestCafeにはHubotのようなwebhookはないのでhapi.jsで簡易的なAPIを作成した。
- リモートURLの生成はドキュメントにもあるように簡単に行える。
createTestCafe('localhost', 1337, 1338) .then(testcafe => { runner = testcafe.createRunner(); return testcafe.createBrowserConnection(); }) .then(remoteConnection => { const url = remoteConnection.url // ここでslackにリモートURLを送信する remoteConnection.once('ready', () => { runner .src('tests/test.js') .browsers(remoteConnection) .reporter('custom-reporter') .run() .then(function () { /* ... */ }) .then(failedCount => { /* ... */ }) .catch(error => { /* ... */}) }); });
※ 抜粋したコードなので詳細はgithubで確認できます