Google Cloud Platform (GCP)を気軽に勉強する
久しぶりのエントリーです。今日はGoogle Cloud Platform (GCP)について書いてみたいと思います。 GCPというのは、Googleが提供するクラウド上のインフラサービスで、簡単なウェブサービスから、ビッグデータ解析、機械学習など幅広い用途に使えることから今大きな注目を集めています。
提供しているサービス
GCPは実に様々なサービスを提供しています。下に例を挙げます。
- Compute Engine
- BigQuery : データウェアハウス
- Cloud Datalab : Jupyter Notebookのような対話型環境
- Cloud Dataflow : データを分散コンピューティングにより高速に処理
- Cloud Strage : ストレージサービス
...
全部書いたらきりがないほどあります。ほかに何があるか見たい方は下のリンクをどうぞ
cloud.google.com
何が良いのか
このサービスを用いることで、例えば企業は自前でサーバーを構築・メンテナンスをする必要がなくなり、設計やコードを書くことに集中できるようになります。また、Googleの計算資源を用いることができるので、GPUやTPUといった、特に機械学習ではうれしいサービスを気軽に利用できるようになります。
料金の壁
しかし、このサービスはもちろん無料ではありません。使った時間と資源の量に比例して料金が発生します。GCPには料金の見積もりサービスもついているので、気になる方は計算してみるとよいと思います。
試しに、個人の趣味で利用することを想定して、Compute Engineを
で見積もってみると料金は$17.53 / monthでした。これが高いと感じるか安いと感じるかは、人によると思いますが、私の場合は、GCP未経験なのでまずは無料で試してみたいと考えました。
GCPには無料トライアルがありますが、やはり最初は基本的なことを教わりながらやりたいと考えていたらCourseraでよいコースがありました。
CourseraのGCPコース
このコースは、Google Cloudが提供しているコースで、全5コースからなり、GCPの基本を学ぶことができます。また、Courseraが提供しているので7日間は無料で受講できます(修了証が欲しければ有料になってしまいますが...)。以下にこのコースの特徴を紹介します。
GCPの全体像を理解できる
全5コースで、GCPを使ったデータ処理の基本を学ぶことができます。計算環境の構築方法から、BigQueryを用いたデータ操作、そしてTensorflowを用いた学習まで一通り学べます。
GCPを実際に使える
これが私にとっては最もありがたかったことですが、コースの課題を解く際にGCPを実際に使うことができます。コースで一時利用用のアカウントを支給してくれるので、実際に講義で学んだことをGCP上で試すことができます。
一つ一つのコースはそんなに重くない
全部で5コースもあると、無料で受講したいと考えている人からすると多すぎるように感じます。しかし、実際には1つ1つのコースはそんなに重くなくて、私の場合には無料期間中に4コース目まで修了できました。
日本語の字幕付き
1コース目はなかった気がするのですが、2コース目からは講義動画で日本語字幕を表示できます。英語が苦手な人でもちゃんと理解ができます。
企業でも最近ではGCPを利用するところも増えていると思うので、勉強しておいて損はないと思います。(最後にとりあえず注意しておくと、Courseraは8日目に入る前に手動でCancel Subscriptionしないと勝手に料金が発生してしまうので、無料にこだわる人はそこだけは注意してください。)
Djangoで家計簿のWebアプリケーションを作る8 Chartjsを使ってグラフを描画する
久しぶりのエントリーです。今回は前々から言っていた、JavaScriptを用いたグラフの描画を行います。これまではPythonのMatplotlibを使ってSVG画像を作り、それをページに表示してきました。これをChartjsを使って書き直すことで、グラフ上にカーソルを移動させたときに値を表示するなどの、画像ではできなかったことを実現します。
前回の記事は下にあります。
pyhaya.hatenablog.com
Chartjsとは
ChartjsとはJavaScriptを使ってHTML上にきれいなグラフを表示させることができるものです。
Chartjsを使ったグラフは公式サイトに様々載っていますが、どれもとてもきれいです。
www.chartjs.org
これはどうしても使ってみたいと思い、試行錯誤してみました。
どうやってDjangoのプロジェクトでChartjsを使うか
ChartjsはプロットするデータをJSON形式で入力します。「Chartjs 使い方」などと検索するとプロットデータをソースコードにべた書きする例がいくつもヒットします。しかし今回の例だとプロットデータはデータベースからとってきたいのでここが悩みの種です。私はこれまでJavaScriptを触ったことがなかったので、ずいぶん苦労しました。
いろいろ試行錯誤した結果、最終的には下のような方法で実現できました。
- Pythonでデータベースからその日ごとの出費を取得する
- PythonでJavaScriptのソースコードを書きだす
- JavaScriptでChartjsを使うコードを書いて描画
調べていると、JSONファイルを出力してそれをJQueryで読み込む例などが出てきたのですが、JS初心者には難しすぎたので(笑)今回は上の方法でやります。
コードを書く
HTMLを編集する
まずはHTMLから整えていきます。前回までのHTMLファイルはさすがにひどかったので今回はBootstrapを組み込んでスタイルを一新しました。
<!DOCTYPE html> {% load static %} <html lang='ja' dir="ltr"> <head> <meta charset="utf-8"> <title>MoneyBook</title> <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script> <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.4/Chart.min.js"></script> <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> <link rel="stylesheet" type="text/css" href="{% static 'moneybook/main_style.css' %}"> <script type="text/javascript" src="{% static 'moneybook/js/data.js' %}"></script> <script type="text/javascript" src="{% static 'moneybook/js/draw.js' %}"></script> <script> $(function () { let dateFormat = 'yy-mm-dd'; $('#date_choice').datepicker({ dateFormat: dateFormat }); }); </script> </head> <body> <div class="container"> <div class="container-inner"> <!-- ヘッダー --> <div class="row top-bg"> <div class="col-md-8" > <h1>{{ year }}年 {{ month }}月</h1> </div> <div class="col-md-2 text-center mt-4"> <a href="/moneybook/{{ prev_year }}/{{ prev_month }}"> <h4> << {{ prev_month }}月</h4> </a> </div> <div class="col-md-2 text-center mt-4"> <a href="/moneybook/{{ next_year }}/{{ next_month }}"> <h4>{{ next_month }}月 >> </h4> </a> </div> </div> <!-- 支出の登録フォーム --> <div class="row"> <div class="card"> <div class="in-card"> <h2>支出の登録</h2> <form action="" method="post" autocomplete="off" style="margin-top:3%;"> {% csrf_token %} {% if form.errors %} {% for errors in form.errors.values %} {% for error in errors %} <div class="alert alert-danger" role="alert"> {{ error }} </div> {% endfor %} {% endfor %} {% endif %} <div class="form-group"> <div class="form-inline"> {{ form.used_date }} {{ form.cost }} {{ form.category }} </div> <hr> <div class="form-inline"> {{ form.money_use }} <input type="submit" name='add' class="btn btn-primary" value="登録"> </div> </div> </form> </div> </div> <!-- 総支出の表示 --> <div class="card"> <div class="in-card"> <h2>今月の総支出</h2> <div class="text-center total_cost"> <h3 class="total">{{ total_cost }}円</h2> </div> </div> </div> </div> <div class="row"> <!-- 支出履歴テーブル --> <div class="card"> <div class="in-card"> <h2>支出履歴</h2> <table style="table-layout:fixed;width:100%;margin-top:20px;"> <thead class="pay_history"> <tr> <th class="date">日付</th> <th class="use">用途</th> <th class="category">カテゴリー</th> <th class="cost">金額</th> <th class="delete">削除</th> </tr> </thead> <tbody class="pay_history"> {% for m in money %} <tr class="table_data"> <td class="date" id="table">{{ m.used_date.month }}/{{ m.used_date.day }}</td> <td class="use" id="table">{{ m.money_use }}</td> <td class="category" id="table">{{ m.get_category_display }}</td> <td class="cost" id="table">{{ m.cost }}円</td> <td class="delete" id="table"> <form action="" method="post"> {% csrf_token %} <input type="hidden" name="used_date" value={{ m.used_date }}> <input type="hidden" name="money_use" value={{ m.money_use }}> <input type="hidden" name="cost" value={{ m.cost }}> <button class="btn btn-outline-danger" type="submit" name="delete"> 削除 </button> </form> </td> </tr> {% endfor %} </tbody> </table> </div> </div> <!-- 支出額の推移グラフ --> <div class="card"> <div class="in-card"> <h2>支出額の推移</h2> <canvas id="data"></canvas> <script type="text/javascript" src="{% static 'moneybook/js/draw.js' %}"></script> </div> </div> </div> </div> </div> <a href='/logout'>LOGOUT</a> </body> </html>
描画に用いるのはdraw.js
という名前のJavaScriptファイルです。
JavaScriptコードを書く
PythonでJavaSciptコードを書くのですがさすがに全部書くのは嫌なので、2つに分けます。
data.js
draw.js
data.js
にプロットするデータが含まれるようにして、他の部分はdraw.js
に書きます。
draw.js
var ctx = document.getElementById("data").getContext("2d"); var myChart = new Chart(ctx, json); #変数jsonはdata.jsに書く
PythonでJavaScriptコードを生成する
ここが核になります。JavaScriptコードを生成するとかいうとなんか難しく聞こえますが open(file_name, 'w')
を使って書き込むだけです。以下がソースコードです。
views.py
from django.shortcuts import render, redirect from django.contrib.auth.mixins import LoginRequiredMixin from django.views import View from django.utils import timezone import calendar import os from .models import ExpenditureDetail from .forms import ExpenditureForm TODAY = str(timezone.now()).split("-") # Create your views here. class MainView(LoginRequiredMixin, View): login_url = "/login" redirect_field_name = "" def get(self, request, year=TODAY[0], month=TODAY[1]): money = ExpenditureDetail.objects.filter( used_date__year=year, used_date__month=month, user_id=request.user.id ).order_by("used_date") total = 0 for m in money: total += m.cost next_year, next_month = get_next(year, month) prev_year, prev_month = get_prev(year, month) context = { "year": year, "month": month, "next_year": next_year, "next_month": next_month, "prev_year": prev_year, "prev_month": prev_month, "total_cost": total, "money": money, "form": ExpenditureForm(), } self.draw_graph(year, month, request.user.id) return render(request, "moneybook/mainview.html", context) def post(self, request, year=TODAY[0], month=TODAY[1]): data = request.POST form = ExpenditureForm(data) if "add" in data.keys(): if form.is_valid(): used_date = data["used_date"] cost = data["cost"] money_use = data["money_use"] category_choices = data["category"] used_date = timezone.datetime.strptime(used_date, "%Y-%m-%d") ExpenditureDetail.objects.create( user_id=request.user.id, used_date=used_date, cost=cost, money_use=money_use, category=category_choices, ) money = ExpenditureDetail.objects.filter( used_date__year=year, used_date__month=month, user_id=request.user.id ).order_by("used_date") total = 0 for m in money: total += m.cost next_year, next_month = get_next(year, month) prev_year, prev_month = get_prev(year, month) context = { "year": year, "month": month, "next_year": next_year, "next_month": next_month, "prev_year": prev_year, "prev_month": prev_month, "total_cost": total, "money": money, "form": form, } self.draw_graph(year, month, request.user.id) return render(request, "moneybook/mainview.html", context) elif "delete" in data.keys(): used_date = data["used_date"] cost = data["cost"] money_use = data["money_use"] used_date = used_date.replace("年", "-").replace("月", "-").replace("日", "") y, m, d = used_date.split("-") ExpenditureDetail.objects.filter( used_date__year=y, used_date__month=m, used_date__day=d, cost__iexact=cost, money_use__iexact=money_use, ).delete() return redirect(to="/moneybook/{}/{}".format(year, month)) def draw_graph(self, year, month, user_id): money = ExpenditureDetail.objects.filter( used_date__year=year, used_date__month=month, user_id=user_id ).order_by("used_date") last_day = calendar.monthrange(int(year), int(month))[1] + 1 day = [i for i in range(1, last_day)] cost = [0 for i in range(len(day))] for m in money: cost[int(str(m.used_date).split("-")[2]) - 1] += int(m.cost) text_day = ",".join(list(map(str, day))) text_cost = ",".join(list(map(str, cost))) json_template = ( """var json = { type: 'bar', data: { labels: [ """ + str(text_day) + """ ], datasets: [{ label: '支出', data: [ """ + str(text_cost) + """ ], borderWidth: 2, strokeColor: 'rgba(0,0,255,1)', backgroundColor: 'rgba(0,191,255,0.5)' }] }, options: { scales: { xAxes: [{ ticks: { beginAtZero:true }, scaleLabel: { display: true, labelString: '日付', fontsize: 18 } }], yAxes: [{ ticks: { beginAtZero:true }, scaleLabel: { display: true, labelString: '支出額 (円)', fontsize: 18 } }] }, responsive: true } } """ ) with open( os.path.dirname(os.path.abspath(__file__)) + "/static/moneybook/js/data.js", "w", ) as f: f.write(json_template) def get_next(year, month): year = int(year) month = int(month) if month == 12: return str(year + 1), "1" else: return str(year), str(month + 1) def get_prev(year, month): year = int(year) month = int(month) if month == 1: return str(year - 1), "12" else: return str(year), str(month - 1)
とりあえずviews.py
の中身を全部載せていますが、描画に関係しているのは一番下のメソッドのみです。ここでやっていることは上のコメントを読めば大体わかると思いますが、JavaScriptコードをstringで用意しておいて、そこにデータを埋め込んでいく形になっています。ダブルクオーテーション3つでstringを作るとformat
関数がうまく動いてくれないのでstringどうしを足しているため汚くなってしまっていますが、やっていることは単純です。
ちゃんと整ったコードは以下のサイトが非常に参考になると思います。
qiita.com
ここまでの結果
ここまでやってページを見てみると、次のようになります。
今話題のものを買い漁ってるふうにしてみました。ちゃんとできてますね。
まとめ
今回は、Chartjsを使ってグラフをより良いものにしました。その方法はJavaScriptコードをPythonから作るというものでした。これは私がJavaScriptわからな過ぎてとった苦肉の策なので、詳しい人から見たらベストプラクティスではないかもしれませんので注意してください(詳しい人いたら教えてください)。ぜんたいのコードは以下の GitHub リポジトリで公開しているのでここに乗せていない部分が気になる方はぜひこちらも見てください!
「How Google Works」を読んだ
Kindleで何か面白そうな本ないかなと探していたら、「How Google Works」という本を見つけました。Googleといえば知らない人がいないほどのIT分野の巨人ですよね。Googleでは社員はどのように働いているのか、そこに興味があって購入してみました。
How Google Works(ハウ・グーグル・ワークス) 私たちの働き方とマネジメント (日経ビジネス人文庫)
- 作者: エリック・シュミット,ジョナサン・ローゼンバーグ,アラン・イーグル,ラリー・ペイジ,土方奈美
- 出版社/メーカー: 日本経済新聞出版社
- 発売日: 2017/09/02
- メディア: 文庫
- この商品を含むブログを見る
誰が書いているのか
著者をみて見ると何人かの共著であることが見て取れます。どの人もググるとわかりますが、Googleの成長に大きな役割を果たした人ばかりです。
エリック・シュミット - Wikipedia
Jonathan Rosenberg (technologist) - Wikipedia
ラリー・ペイジ - Wikipedia
訳者は土方奈美という方です。訳者に関しては詳しくないのですが、翻訳本でよくある「訳がクソ」という感じは全くありませんでした。
どんな本だったか
私は、この本にエンジニア視点からの働き方というのを期待していました。しかし、実際に読んでみると、どちらかというと経営者の視点からの本でした(副題に「私たちの働き方とマネジメント」とあるので当たり前といえば当たり前なのですが...)。なので、最初はミスったかなと感じながら読んでいたのですが、読んでいるうちに考えは変わっていきました。本の中では繰り返しIT分野の成長がほかの分野と比べていかに急速かというのを述べていますが、このように状況が目まぐるしく変化するような状況では、エンジニアも経営者のような全体を俯瞰するような視点が必要なのだと感じます。
本書では、いくつかのテーマについて、Googleがどのような歴史をたどってきたかも見ながら解説しています。そのテーマとは
- 文化
- 戦略
- 人材
- 意思決定
- コミュニケーション
- イノベーション
です。全体を読んでいて感じたのは、Googleがいかにエンジニアを大事にしているかということです。どうすれば優秀なエンジニアを雇うことができるか、どのような環境を作ればエンジニアの能力を最大限発揮させることができるか、そんな考えが一貫して根底にあるような感じがしました。
そして、この本ではGoogleの手掛けてきたプロジェクトがどのようにして進んできたのか、おなじみのGmailやGoogle earthなどの例を紹介しています。いくつもの事例を読みながら感じたのは、Google内部での上下関係の希薄さとスピードを重視する姿勢です。ITは目まぐるしいスピードで日々進歩しているのでこのような姿勢が、プラットフォーマーであり続けるために必要不可欠であるということを感じさせられます。
こんな感じのことばかり書いてあると、読んでいて「Google入りてぇ」ってなって勝手に勉強のモチベーションが上がりますwww。同時に日本企業もこんな感じにならないかなとも思うのですが、日本はアメリカとはバックグラウンドが異なるので猿真似してもうまくいかないでしょう。日本企業はこれからどうするべきなのか、そんなことにも考えが行く本でした。
AtomとVSCodeのJupyter Notebook環境を比較した
仕事でよくJupyter Notebookを使ってのデータ解析を行うのですが、最近いちいちブラウザでNotebookを起動するのが面倒になってきました。そこでエディタを使ってJupyter Notebookを使おうと思って色々調べていたら、AtomとVSCodeを使うのがどうやらよさそうだということがわかりました。
そこで、この記事では、両方の環境を使ってみてどちらがより使いやすいか比較してみたいと思います。
環境構築
PythonとJupyter Notebookを使いたい場合には、Anacondaで入れてしまうのが一番簡単です(特にWindowsでは)。
weblabo.oscasierra.net
エディタをインストールする
AtomやVSCodeのインストール方法は下のリンクからダウンロード、インストールするだけです。
atom.io
code.visualstudio.com
Python環境を整える
次に各エディタにPythonの環境を整えていきます。
Atomの場合:
https://hajipro.com/python/atom-python
手順はAtomのほうが圧倒的に楽です。
Jupyter Notebookを使う環境を整えていく
使ってみる
いよいよ使ってみます。どちらのほうが使い勝手が良いでしょうか?
普通に使ってみる
まずは普通に、算術計算に使ってみます。次のコードを実行してみます。
x = 1 y = 2 x + y
両方ともコードブロックはブロックの先頭に#%%
を入れることで表現できます。
グラフを書く
下のコードを実行してみます。
import numpy as np import matplotlib.pyplot as plt x = np.linspace(0, 10, 500) y = np.sin(x) plt.figure() plt.plot(x, y) plt.show()
Atom
きれいに出力されました。
機械学習を原理から理解する 回帰
機械学習をただ使うだけでなく、どのような原理で動いているのか理解するために数学的な観点からちゃんとアルゴリズムを見てみます。
前回は線形回帰の記事を書きました。
pyhaya.hatenablog.com
今回は、前回の線形回帰のアルゴリズムを拡張して、高次の回帰をしていきます。
前回の復習(線形回帰)
前回はデータ点を直線で回帰するために、最小二乗法を使った方法について説明しました。ここでは回帰直線を
というように重みパラメータとデータの内積で表現しました。そして、適切な重みを見つけるための指針として正解との二乗誤差
を最小にしようということになり、行列計算によってこのような重みパラメータを得られることを見ました。
高次多項式での回帰
前回の記事だけでも、複数の特徴量の線形和の形での回帰は可能です。今回はさらにバリエーションを増やすために高次多項式での回帰を行います。つまり、回帰曲線は下のような式であらわされます。
見やすさのため変数は1つのものを書いています。また、いつも通りバイアスは重みパラメータ内部に入れています。
理論
実は、理論的にはこの回帰は線形回帰と同一の方法で実行することができます。というのも、(3)式はベクトルの形式で書き直すと
ここでは
です。最後の1はバイアスを重みパラメータの中に取り込んだために出てきた項です。この形は線形回帰でやったものと全く同じです。なので同じアルゴリズムで解くことができます。
添え字は訓練データの番号です。
実装(Python3)
上の理論を見ながらPythonで実装を行ってみます。
import matplotlib.pyplot as plt import numpy as np from sklearn.model_selection import train_test_split from sklearn.datasets import make_regression def Regression(X, y, dim): d = X.shape[1] + 1 A = np.zeros((d+dim-1, d+dim-1)) b = np.zeros((d+dim-1, 1)) for i in range(len(X)): x = [] for j in range(1, dim+1): x += list(X[i]**j) x.append(1) x = np.array(x).reshape((d+dim-1, 1)) A += x.dot(x.T) b += y[i] * x return np.linalg.inv(A).dot(b) if __name__ == "__main__": X, y = make_regression( n_samples=50, n_features=1, noise=4, random_state=42 ) dim = 2 #何次の多項式で回帰を行うか pred = Regression(X, y, dim) x = np.linspace(-2, 1.9, 50) y_pred = np.zeros_like(x) for i in range(0, dim): y_pred += pred[i][0] * x**(i+1) y_pred += pred[dim][0] plt.figure() plt.plot(X, y, 'o') plt.plot(x, y_pred) plt.show()
回帰結果を見てみる
回帰結果を次数を変えてみてみます。まずは二次の場合フィッティング結果は下のようになります。
回帰はうまくいっている印象です。次に次数を増やしてみます。十次のとき
明らかにノイズも拾って回帰してしまっています。これは訓練データを過度に学習してしまっている状況で、過学習状態となっています。
機械学習を原理から理解する 線形回帰
最近始めた機械学習の理論的な話の3つめです。相変わらず線形の話が続きます。過去記事を下に貼っておきます。
pyhaya.hatenablog.com
pyhaya.hatenablog.com
今回は、前回までの分類アルゴリズムとは異なり、回帰アルゴリズムについて書きます。
回帰とは
回帰というのは、データセットがあったときにパラメータと目的変数の間に成り立つ関係式を見つける操作です。実験とかで結果を理論曲線でフィッティングするイメージです。
...もう少し具体的な例を出すと、例えば、パラメータとして、過去1カ月分の降水量と気温、天候が与えられたときにある特定の日に収穫されたリンゴの糖度(目的変数)を予測するとかなると回帰を使います。降水量、気温、天候というデータを組み合わせることでリンゴの糖度を表現するわけです。
線形回帰
線形という表現からもわかるように、回帰の式に一次関数を使うのが線形回帰です。例えばパラメータが1つあって(xと書く)、そこからある変数の値(yと書く)を予測したいとします。いま、学習のためにいくつかデータが与えられていて、下のグラフのように分布しているとします。
これはおおむね線形に分布していることがわかります。これを一変数の一次関数で回帰したのがオレンジの直線になります。
回帰直線を求める
パラメータ
線形回帰をする際に求める必要があるのは次の2種類のパラメータです。一つは重みパラメータそしてもう一つがバイアスです。回帰に使うパラメータが1種類の時には重みパラメータは2次元平面上の直線の傾きを表します。
ここでは与えられている学習データで、がそのデータから計算した目的変数の予測値です。ふつうは、バイアスも重みパラメータの中に取り込んだ下のような形を使います。
ここで,
損失関数
回帰の際には、どれくらいうまく回帰ができているかを知るために損失関数を定義します。回帰によってこの損失関数を最小化することが目標になります。
線形回帰の際によく用いられる損失関数は二乗誤差です。二乗誤差は下のように定義されます。
は学習データの数です。
Pythonで書く
上の計算をPythonのコードに書き下すと下のようになります。
import matplotlib.pyplot as plt import numpy as np from sklearn.model_selection import train_test_split from sklearn.datasets import make_regression def LinearRegression(X, y): A = np.zeros((X.shape[1]+1, X.shape[1] + 1)) b = np.zeros((X.shape[1]+1, 1)) for i in range(len(X)): x = list(X[i]) x.append(1) x = np.array(x).reshape((2, 1)) A += x.dot(x.T) b += y[i] * x return np.linalg.inv(A).dot(b) if __name__ == '__main__': X, y = make_regression( n_samples=50, n_features=1, noise=7, ) w = LinearRegression(X, y) fit_x = np.linspace(-2, 2, 500) fit_y = fit_x * w[0, 0] + w[1, 0] plt.figure() plt.plot(X, y, 'o') plt.plot(fit_x, fit_y) plt.show()
9, 10行目でX.shape[1]+1
のように+1しているのは、バイアスを重みパラメータの中に取り込むために、配列の要素数を1つ増やすためです。
このコードでは、簡単のために変数は1種類にしています。そのため計算結果は簡単に図示することができて、
うまくいっていることが見て取れます。
逆行列が存在しないとき
ここからは、少しだけ発展的な内容になります。
このアルゴリズムでは、計算に逆行列を使っています。しかし、逆行列は必ずしも存在するとは限りません。このようなときにはどのように計算すればよいか考えます。
逆行列が存在しないときというのは、線形代数の知識を使えば固有値に0を含むときであることがわかります。このようなときに行列Aを
のように直交行列と固有値を対角成分にもつ対角行列を使って分解することができます。の対角成分に0が含まれなければ、Dのすべて対角成分をすべて逆数にした行列がDの逆行列になり、実際、
のように、真ん中から次々に単位行列に代わっていきます(直交行列は転置して複素共役をとったと掛けると単位行列になる性質を利用しています。 Wiki参照)。
対角成分に0が含まれていては、このような操作は行うことができません。この時にはを次のように定義します。
要するに、逆数とれるならとって、とれないなら0のままにしておくということです。このようなによって計算されるを
として逆行列の代わりに使えば、
となります。は中の列ベクトルです。最右辺を見てみると、がであるようなについてのによって張られる空間への射影になっていることがわかります。このように空間を制限して考え直すことで、逆行列が存在しない場合においても回帰計算をすることが可能になります。
機械学習を原理から理解する パーセプトロン
内容的には、前回の記事の続き的な感じになります。
pyhaya.hatenablog.com
今回は、二値分類をパーセプトロンを使って実装します。パーセプトロンといえば、ニューラルネットワークやディープラーニングの基本という感じの学習器です。前回に引き続き、理論を説明した後にPythonでの実装を示します。
対象とする問題
いくつかの特徴量が与えられていて、それに対して正解ラベルが+1, -1で与えられているような状況を考えます。考える仮説空間はhalf-spacesクラス(日本語訳がわからない)です。
仮説空間という言葉が出てくると難しく感じますが、予測に使うのは下のような関数です。
は重みとバイアスです。基本的には線形関数で、最終的にsignで符号だけ取り出すことで予測値を算出しています。
どのように解くか
前回の記事では、これを線形計画問題だと考えて解きました。今回は、線形計画に比べると直感的な方法でパラメータを求めていきます。
まず、パラメータをで初期化します。なぜこのような値で初期化するかというと、こうすることで
となって、ひとまず最初のデータに関して学習させることができるためです。
次にやることははこれで最適か調べることです。学習データを(2)式に代入していって、不等式が成り立っているか確かめます。すべての学習データで不等式が成立していればこのパラメータが最適値とすることができます。しかしほとんどの場合はこううまくはいきません。番目のデータで
となってしまった場合を考えましょう。どうすればよいでしょう。要するに、左辺を増やす方向にを更新すればよいです。どうやるかというと、
このとき、(4)式の左辺は
となります。ここで、正解ラベルは±1しかとらないことを使いました。無事左辺を増やせました。このようにして、すべての学習データについて(2)式が成り立つまで値の更新を続けます。
Pythonでの実装
値の更新は、現実問題としてすべての学習データを間違えることなく予測することは不可能なことが多いので、適当なところで打ち切ります。100回の値更新で打ち切るようにしたのが下のプログラムです。
import numpy as np from sklearn.model_selection import train_test_split from sklearn.datasets import make_classification def Perceptron(X, y): X = np.array(X) w = np.zeros(len(X[0])) w = y[0] * X[0] for count in range(100): success = True for i in range(len(X)): if np.sign(w.dot(X[i])) == np.sign(y[i]): continue else: w += y[i]*X[i] success = False break if success: break return w if __name__ == '__main__': #サンプル生成 X, y = make_classification( n_samples=500, n_classes=2, n_features=2, n_redundant=0, class_sep=1.5, ) y = list(map(lambda x: -1 if x == 0 else 1, y)) X = np.array(X) y = np.array(y) X_train, X_test, y_train, y_test = train_test_split(X, y) w = Perceptron(X_train, y_train) accuracy = np.sum(y_test*X_test.dot(w) > 0) print(accuracy / len(X_test))
これを実行すると、だいたい95%程度の精度が出ていることが確認できます。