2016年12月18日 星期日

iOS筆記:在Swift中使用Objective-C的專案

在使用Swift開發的時候, 一定會遇到只用3rd party專案的情形發生. 但也不是每個open source的專案都會轉到Swift中進行開發. 所以這邊紀錄一下. 怎麼在Swift中使用3rd party的Objc-C專案.

Objective-C Bridging Header File

不管是[在Swift專案中使用Objective-C檔案]或是[在Objective-C專案中使用Swift檔案]都需要使用Objective-C bridging header檔案。加入Objective-C bridging header檔案的方法有兩種:
  1. 由Xcode 自己產生
    首先在File -> new file中, 點選Objective-C file (在objc-c 中就是新增.swift的檔案), 名稱可以隨便亂取, 我們主要只是為了再新增檔案的時候會跳出下面的視窗.
enter image description here.

按下 Create Bridging Header後, 就可以把新增的專案刪除了.
  1. 自己手動新增
    如果沒跳出上面的視窗或者是點錯的話就可以在File -> New File。然後選[iOS](或是watchOS或是tvOS或是OSX)下的[Source]裡面的[Header File]自己產生一個標頭檔。
    名稱的話我會跟著下面的方式照打上!
    [Your project name]-Bridging-Header
如果都沒有跳出新增的畫面的話. 可以去專案的 [Build Settings] 下, Swift Compiler – Code Generation一欄中,新增Objective-C bridging header檔案的位置。

enter image description here

新增完之後…

好了之後就是要進行import的動作了.

enter image description here



2016年11月26日 星期六

ML初學筆記:Keywords 筆記

這裡主要是針對流程做一個紀錄, 也可以說是一個筆記, 所以會收錄一些從別人那轉載過來一些文字敘述或是圖等. 來源皆在最下面的網址中. 謝謝!

這次主要針對Google的TensorFlow作為機器學習的入門對象, 在學的時候有出現很多單字, 因此在這邊就紀錄下他們在TensorFlow中代表的意義是什麼.

TensorFlow

TensorFlow為Google開發的一個開源的機器學習工具, 官網有很多解釋以及相關的範例帶你上手這一個強大的工具.
架構如下圖所示, 支援Python跟C++. 在 Python 语言中, 返回的 tensor 是 numpy ndarray 对象; 在 C 和 C++ 语言中, 返回的 tensor 是 tensorflow::Tensor 實例.
主要是跑在Linux&Mac系統上. Windows在執行的時候必須先用虛擬機再安裝Linux才行.
enter image description here
執行的流程, 最大主題可以分成兩塊:
一:模型定義(modeling):抽象化,此階段完全是紙上談兵
1. 輸入項、輸出項的定義(tensorflow的術語叫:placehold)
2. 模型建構(隱藏層)
a. 定義 Variable (例:權重、誤差項)
b. 定義網路 (例:y = W*X+b)
3. 初始化 Variable
4. 定義Loss函數
5. 最佳化:讓Loss函數最小化(Tensorflow 內建 Gradient Desecnt 等,可直接叫用)
二:執行(session階段):計算資源投入(CPU/GPU)、實際資料投入
1. 開啟 Session 來run Graph
2. 要跑幾次去達成最佳化(Iterative Update)
3. 訓練資料讀入( feed)

Tensor

Tensor可以看作是一个 n 维的数组或列表. 計算Graph中, 操作間傳遞的數據都是 Tensor.

Operation

Graph 中的節點,也就是對資料的操作/計算

Variable

A variable maintains state in the graph across calls to run(). You add a variable to the graph by constructing an instance of the class Variable.
(Variable is something that can updated as your training. )

Placeholder

A input into neural network.

Data Flow Graphs

Data Flow Graphs使用節點(Nodes)與邊(Edges)所繪出的有向圖(Directed Graph)來描述數學運算[如下圖所示]。Nodes一般是用來實作數學運算,也可以儲存資料、運算結果或持久性變數,Edges描述Nodes之間的I/O關係。
這些數據的Edges進行動態調整多維數據陣列(或Tensors)的大小。TensorFlow的命名由來是Graph**[表示一整個計算任務]**上,Tensors的Flow在Nodes間藉著Edges穿梭傳遞。當系統中Nodes的所有Tensors從輸入端Edges取得,Nodes就被分配到的運算裝備中,Nodes可非同步且平行的方式作業。
enter image description here

Session

在 Session 階段中才會執行 graph.

Loss function

比較預估以及真實結果之間的差異, 在一開始的範例常使用下列的”Cross Entropy”.

Optimizer

根據Loss function的值 導入學習率去修正誤差.

Cross-Entropy

Entropy: 熵的概念最早起源於物理學,用於度量一個熱力學系統的無序程度。在資訊理論裡面,熵是對不確定性的測量。但是在資訊世界,熵越高,則能傳輸越多的資訊,熵越低,則意味著傳輸的資訊越少。
大多數人都會對犯錯感到不愉快。當發生錯誤的時候,可能當下會覺得非常的悲劇,但是如果可以記得這次的教訓下一次再遇到不犯錯其實就可以了. 且當犯的錯越大的時候,一般人對這錯誤的印象會記很久以避免再次的犯錯,相反,如果錯誤很不明顯的話,我們的學習改進的速度可能將會很慢。
對於電腦來說我們也是希望它在學習時,當發生錯誤的時候所做的修正就應該要越大. 而這就是使用Cross Entropy的一個優點,可以參考名稱的超連結有更近一步的介紹.

Gradient Descent

迴歸方程式是一次方程式, 所以是一條線, 而Loss Function是計算最小平方和,是自變項的二次方程式(下圖右),所以是個曲線(如果是多元迴歸就是一個高維度的山谷)
enter image description here
那做法就是將問題從線性方程式轉換成求最小平方和的最佳解.要求最佳解用的方法是,先隨便站在山谷上的任何一點,發現哪邊比較低,就往哪邊走. 透過多次的迭代來找到最低點,化成公式就長得像下面這樣:
enter image description here
當θ落在最小值的右邊,斜率是正的,帶到公式中,就會減少θ值;反之,如果θ比最小值還小,那斜率是負的,帶到迭代公式中就會增加θ值.由於曲線越接近谷底越為平緩,所以當θ靠近最低點時,每次移動的距離也會越來越小,最終將收斂在最低點.
enter image description here
講完微分的部分,接下來是α值(learning rate).α值的用途主要在控制每次θ移動的距離,下圖顯示移動距離太大或太小的狀況:
enter image description here
如果一次移動太短的距離(左圖),那收斂速度會相當的慢.但是如果移動的距離太大(右圖),反而可能造成無法收斂的情形.所以會需要根據不同的狀況來調整這個α的大小.
References:
1. http://mropengate.blogspot.tw/2016/10/ai-ch165-tensorflow.html
2. http://www.slideshare.net/ckmarkohchang/tensorflow-60645128
3. http://tensorflow-tw.blogspot.tw/2015/12/tensorflow.html
4. http://blog.moebigdata.com/2016/01/tensorflow.html
5. https://hit-scir.gitbooks.io/neural-networks-and-deep-learning-zh_cn/content/chap3/c3s1.html

2016年11月18日 星期五

ML初學筆記:安裝Tensorflow

這裡主要是針對流程做一個紀錄, 也可以說是一個筆記, 所以會收錄一些從別人那轉載過來一些文字敘述或是圖等. 來源皆在最下面的網址中. 謝謝!

安裝步驟

參考Tensorflow官網的安裝流程, 按照以下步驟就可以在Mac上安裝當下最新的版本.

$ virtualenv --system-site-packages ~/tensorflow
$ source ~/tensorflow/bin/activate
(tensorflow)$  # Your prompt should change

(tensorflow)$ export TF_BINARY_URL=https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-0.11.0-py2-none-any.whl

(tensorflow)$ pip install --upgrade $TF_BINARY_URL

在這邊我是參考在Youtube上看的Tutorial .

(tensorflow)$ pip install --upgrade jupyter
(tensorflow)$ pip install --upgrade Pillow

接著去下載在Github上的課程文件. 執行完下面的指令後, 存取課程文件即可.

(tensorflow)$ jupyter notebook

enter image description here

Virtualenv

Virtualenv 可以隔離函數庫需求不同的專案,讓它們不會互相影響。在建立並啟動虛擬環境後,透過 pip 安裝的套件會被放在虛擬環境中,專案就可以擁有一個獨立的環境,也降低了不同版本套件間衝突的可能。

簡而言之,Virtualenv 可以幫你做到:
1. 在沒有權限的情況下安裝新套件
2. 不同專案可以使用不同版本的相同套件
3. 套件版本升級時不會影響其他專案

使用方法
I. 建立虛擬環境
請於命令列模式下輸入下列指令:

$ virtualenv [指定虛擬環境的名稱]

例如下列指令會建立名為 “ENV” 的虛擬環境:

$ virtualenv ENV

預設在建立虛擬環境時,會依賴系統環境中的 site packages,如果想完全不依賴系統的 packages,可以加上參數 –no-site-packages 來建立虛擬環境:

$ virtualenv --no-site-packages [指定虛擬環境的名稱]

II. 啟動虛擬環境
請先切換當前目錄至建立的虛擬環境中。前例中,建立名稱為 “ENV”,則:

$ cd ENV

接著,啟動虛擬環境:

$ source bin/activate

III. 退出虛擬環境

$ deactivate

Reference:
1. https://www.tensorflow.org/
2. https://github.com/sherrym/tf-tutorial/blob/master/install-mac-native.md

2016年8月25日 星期四

Git筆記:gitignore

通常在一般的專案中一定會有引用Library的時候, 但通常這些library伊多, 就會造成整個資料夾過肥, 但這些檔案都是可以透過使用者在本地端下載即可, 所以在這時就可以使用.gitignore來將不需要上傳的檔案留在原地.

iOS的部分可以參考以下網址:
1.Objective-C.gitignore
https://github.com/github/gitignore/blob/master/Objective-C.gitignore
2.Stack
http://stackoverflow.com/questions/49478/git-ignore-file-for-xcode-projects
3.Gitignore產生器
https://www.gitignore.io/
4.一些程式語言的參考
https://github.com/github/gitignore

SourceTree

可以手動加入.
enter image description here

手動新增.gitignore

利用VIM新增檔案即可.

PUSH後才新增時

會發現.gitignore沒有作用, 原因是因為.ignore是後來加進來的 local cache裡面記錄的這個文件是不會忽略的, 所以需要進行緩存清除

$ git rm --cached iLedger.xcodeproj/project.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate
$ git commit -m "Removed the stupid strange file that shouldn't be tracked"
$ git push

2016年7月31日 星期日

Node.js筆記:Node.js with Socket.io

WebSocket:

為一個從HTML5中開始的一個協定, 瀏覽器和伺服器之間只需要一個handshaking動作就形成了一條快速通道. 兩者之間可以直接互相傳送資料.
這種方式可以避免無謂的浪費伺服器頻寬和資源(舊式的是一段時間後再發一次request.)

Socket.io

一般的http協定不支援長久的連接, 需要不斷地發送request給server更新資料. 而Socket.io可以達到 server 和 client 的雙向溝通, 只要一建立連線 server 可以一直傳訊息給 client.

ChatRoom Example

1.新增Chatroom資料夾並新增package.json
首先新增一個Chatroom的資料夾, 並執行以下指令安裝相關套件.

    {
      "name": "socket-chat-example",
      "version": "0.0.1",
      "description": "my first socket.io app",
      "dependencies": {}
    }

2.安裝express & Socket.io

    $npm install --save express
    $npm install --save socket.io

3.新增index.js
第一行: 建立一個express module的app function.
第二行: 建立 http 物件
第三行(app.get…): 處理 / (根目錄) 的 URL GET 要求

sendFile為傳輸檔案, 在這裡傳的是index.js所在資料夾底下的index.html檔
__dirname: 返回被執行的.js檔所在資料夾的絕對路徑
最後則是監聽PORT:3000

    var app = require('express')();
    var http = require('http').Server(app);

    app.get('/', function(req, res){
      res.sendFile(__dirname + '/index.html');
    });

    http.listen(3000, function(){
      console.log('listening on *:3000');
    });

4.建立Socket.io
首先導入Socket.io模組, 因 Socket.io 依賴 HTTP 所以這裡採用一併引入的方式.

socket.io 的核心函數:

emit :用來發射一個事件或者說觸發一個事件, 第一個參數為事件名,第二個參數為要發送的數據, 第三個參數為callback function.
on :用來監聽一個emit 發射的事件, 第一個參數為要監聽的事件名,第二個參數為一個callback function用來接收對方發來的數據.

如上述的介紹這裡的io.on就可以解釋為使用 on() 函數將特定的事件連接到指定的匿名函數(用來定義當伺服器接收到請求時,該做什麼事情,以及該如何回應), 藉此定義整個資料傳輸過程要如何運作。

而connection/connect事件則為socket.io默認的事件之一.
在這邊socket.io提供的interface是基於事件的,server端監聽”connectione”事件, 如果接收到client的連結請求, 就會進入callback function,而連上的socket就會開始監聽事件,事件的名稱就是’chat message’,當socket發出’chat message’事件時就會執行所寫的程式內容,以下面為例,當觸發事件後會針對io所有的client socket發出’chat message’事件以及其後發所帶的msg參數。

io.on為監聽跟client是否達成連線, 內圈的socket.on則是監聽client的事作而回覆對應的function, 通常這裡的事件也需要在client端建立同樣的事件名稱才行.

    var io = require('socket.io')(http);

    io.on('connection', function(socket){
        socket.on('chat message', function(msg){
          io.emit('chat message', msg);
        });
    }); 

5.撰寫index.html
在使用 Socket.io 時, 伺服器端與客戶端都必須要有它的 JS 檔案才有辦法運作, 而在上一步已經為server端裝上 Socket.io module了, 接下來就是為client端, 也就是在html中裝上模組.

裝法為在 HTML 頁面中加入以下這段程式碼.

    <script src="https://cdn.socket.io/socket.io.js"></script>

在Client端一樣要先註冊一個socket, 並建立監聽Server的’chat message’事件。
完整如下:

    <!doctype html>
    <html>
      <head>
        <title>Socket.IO chat</title>
      </head>
      <body>
        <ul id="messages"></ul>
        <form action="">
          <input id="m" autocomplete="off" /><button>Send</button>
        </form>
        <script src="/socket.io/socket.io.js"></script>
        <script src="http://code.jquery.com/jquery-1.12.4.js"></script>
        <script>
          var socket = io.connect();
          $('form').submit(function(){
            socket.emit('chat message', $('#m').val());
            $('#m').val('');
            return false;
          });
          socket.on('chat message', function(msg){
            $('#messages').append($('<li>').text(msg));
          });
        </script>
      </body>
    </html>

參考:
1. http://socket.io/get-started/chat/
2. https://blog.allenchou.cc/socket-io-nodejs/
3. http://socket.io/docs/server-api/

2016年7月3日 星期日

Node.js筆記:Blog system(3)

新增資料庫功能

先在專案中新增一個”lib”的資料夾, 並新建一個檔案”db.js”. 在這裡我們要使用mongoose來存取MongoDB.

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var Blog = new Schema({
    Username: String,
    Article: String,
    CreateDate: Date
});

var Comment = new Schema({
    Visitor: String,
    Comment: String,
    MessageID: Schema.Types.ObjectId,
    CreateDate: Date
});

mongoose.model( 'Blog', Blog );
mongoose.model( 'Comment', Comment );
mongoose.connect('mongodb://localhost/blog');

在上面我們新增的兩個model, 一個叫Blog, 一個是Comment. 最後一行則是連線到本機端的MongoDB伺服器.

接著修改apis.js初始化資料庫
主要是利用require把資料庫設定載入, 再把定義好的model也加入.

require('../lib/db');

var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Blog = mongoose.model('Blog');
var Comment = mongoose.model('Comment');

...

支線任務:
1.brew update error.
問題簡述在此:https://github.com/Homebrew/legacy-homebrew/issues/49879
解決方式就是利用下面兩組command就可以讓brew正常運作.

    $sudo chown -R $(whoami):admin /usr/local
    $cd $(brew --prefix) && git fetch origin && git reset --hard origin/master

2.MongoDB使用.
安裝:

//Step 1 - Installation ( Don't follow this step if you have already installed MongoDB ):
brew update
brew install mongodb

//Step 2 - Run Mongo Daemon:
mkdir -p /data/db
sudo mongod

//Step 3 - Run Mongo Shell Interface:
mongo

新增功能

1.首頁列出所有文章
修改routes/index.js阿

利用find()找出資料庫中所有的資料, 並把資料存在blogs物件中, 最後把blogs物件回傳給頁面.

require('../lib/db');

var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Blog = mongoose.model('Blog');

router.get('/', function(req, res, next) {
    res.locals.username = req.session.name ;
    res.locals.authenticated = req.session.logined;
    Blog.find(function (err, blogs, count) {
      res.render('index', {
        title : 'Blog System';
        blogs : blogs;
      });
    });
});

module.exports = router;

修改index.jade, 利用forloop把blogs物件的資料都列印在頁面上.

...
div(style='padding:50px;')
        h1= title

        for article in blogs
                div
                    div(style='float:left;margin-top:10px;')= article.Username
                    div(style='float:right;margin-top:10px;')= article.CreateDate
                    br
                    div(style='clear:both')= article.Article
                        a(href='/users/message/'+article.id, style='float:right;margin-top:105px;margin-right:20px;') Leave a message
                    hr(style='margin-top:110px;')

2.新增文章
views/users/add_article.jade

doctype html
html
    head
        title= Login
    body
        div(style='float:left;margin:10px;')
            a(href='/') Home

        div(style='float:right')
            if (username && authenticated)
                span #{username}
                a(href='/users/profile', style='margin:10px;') Profile
                a(href='/users/signout', style='margin:10px;') Sign Out
            else
                a(href='/users/signin', style='margin:10px;') Log In
                a(href='/users/register', style='margin:10px;') Register
                a(href='/users/forget', style='margin:10px;') Forgot password

        div(style='padding:50px;')
            form(name="add", action="/apis/add", method="post")
                h1 Add Article
                div
                    p
                        span Writer:
                        label(style='padding-right:50px') #{username}
                    p
                        textarea(cols="50", rows="25", style='resize : none;', name="Content")
                    div
                        span(style='padding-right:377px')
                        input(type="submit", name="submit", value="Save")

後半段為一個POST方法, 當按下送出按鈕後會執行 /apis/add 的網址.

修改users.js跟apis.js
下面先判斷使用者是否有登入, 如果有登入才能進入新增文章.

#/users/js

router.get('/add_article', function(req, res, next) {
  if((!req.session.name) || (!req.session.logined)) {
    res.redirect('/');
    return;
  }
  res.locals.username = req.session.name;
  res.locals.authenticated = req.session.logined;
  res.render('users/add_article');
});

...
#/apis.js

router.post('/add', function(req, res, next) {
  if (!req.session.name) {
      res.redirect('/');
      return;
  }
  new Blog({
      Username: req.session.name,
      Article: req.body.Content,
      CreateDate: Date.now()
  }).save( function( err ){
      if (err) {
          console.log('Fail to save to DB.');
          return;
      }
      console.log('Save to DB.');
  });
  res.redirect('/');
});

enter image description here

2016年7月1日 星期五

Node.js筆記:Blog system(2)

Express generation 專案架構

當用express generation指令後會產生一個node project的資料夾.
enter image description here

  • app.js: 開機檔案, 或是說入口網站
  • bin: 存放執行檔
  • node_modules: 存放package.json中安裝的模組, 當你在package.json中新增相依的模組並安裝後, 該模組會存放在這個資料夾下.
  • package.json: 儲存著專案的資訊以及模組相依(dependencies), 當在dependencies中新增相依的模組時, 執行npm install, npm會檢查目前的資料下的package.json, 並自動安裝所有指定的模組
  • public: 存放image, css, js等文件
  • routes: 存放路由文件
  • views: 存放視圖檔, 或是說範本檔案

配置Routing

規劃網站各個頁面的路由路徑. 首先再次修改app.js: 新增apis路由, 用來負責POST方法的處理.

    ...
    var routes = require('./routes/index');
    var users = require('./routes/users');
    var apis = require('./routes/apis');

    ...
    app.use('/', routes);
    app.use('/users', users);
    app.use('/apis', apis);

設定user路由

在我們的設定中, user路由負責處理所有來自/user網址的要求, 例如新增文章, 會員註冊等. user路由會視用戶的請求而回應不同的資料.

新增首頁

首先在views資料夾中建立一個users資料夾. 裡面會放置所有user路由會用到的頁面.

首頁

1.更新index.jade檔
中間的if判斷是會根據使用者是否登入而顯示不一定的連結給使用者.

    doctype html
html
    head
        title= 'Blog System'
    body
        div(style='float:left;margin:10px;')
            a(href='/') Home

        div(style='float:right')
            if (username && authenticated)
                a(href='/users/signout', style='margin:10px;') Sign Out
                a(href='/users/add_article', style='margin:10px;') Add Article
                a(href='/users/profile', style='margin:10px;') Profile
                span #{username}
            else
                a(href='/users/signin', style='margin:10px;') Log In
                a(href='/users/register', style='margin:10px;') Register
                a(href='/users/forget', style='margin:10px;') Forgot password

    div(style='padding:50px;')
        h1= title

2.修改routes/index.js
把session所存的變數所存的變數傳回頁面.

    var express = require('express');
    var router = express.Router();

    /* GET home page. */
    router.get('/', function(req, res, next) {
        res.locals.username = req.session.name ;
        res.locals.authenticated = req.session.logined;
        res.render( 'index', {title : 'Blog System'});
    });
    module.exports = router;

使用者註冊頁面

1.新增views/users/register.jade
這邊的重點是寫了一個form的POST表單來處理使用者設定帳密

doctype html
html
    head
        title= Register
    body
        div(style='float:left;margin:10px;')
            a(href='/') Home

        div(style='float:right')
                a(href='/users/signin', style='margin:10px;') Log In
                a(href='/users/forget', style='margin:10px;') Forgot password

        div(style='padding:50px;')
            form(name="login", action="/apis/login", method="post")
                h1 Register
                div
                    p
                        label(style='padding-right:50px') Username or e-mail
                        input(type="text", name="user", placeholder='username@example.com')
                    p
                        label(style='padding-right:110px')  password
                        input(type="password", name="passwd")
                p
                    span(style='padding-right:260px')
                    input(type="submit", name="submit", value="Sing Up")

2.修改routes/user.js
新增一個判斷式, 如果logined是true, 代表使用者已經是登入狀態, 就會直接回到首頁.
如果尚未登入則會顯示register.jade的頁面.

var express = require('express');
var router = express.Router();

router.get('/register', function(req, res, next) {
    if (req.session.logined) {
        res.redirect('/');
        return;
    }
    res.render('users/register');
});

    ....

3.修改routes/apis.js
新增login函數, 處理註冊頁面傳來的POST方法.
先利用if來判斷使用者是否有輸入帳密, 當其中少一個的話則返回註冊畫面.
當輸入正確就把帳密給儲存到session中. 並把login設定為true代表使用者登入了.

ar express = require('express');
var router = express.Router();

router.post('/login', function(req, res, next) {
    if ((!req.body.user) || (!req.body.passwd)) {
        res.redirect('register');
        return;
    }
    req.session.name = req.body.user;
    req.session.passwd = req.body.passwd;
    req.session.logined = true;
    res.redirect('/');
});

...

使用者登入畫面

1.使用者登入頁面跟註冊的register.jade比, 只有差在字改成”Login”.

doctype html
html
    head
        title= Login
    body
        div(style='float:left;margin:10px;')
            a(href='/') Home

        div(style='float:right')
                a(href='/users/register', style='margin:10px;') Register
                a(href='/users/forget', style='margin:10px;') Forgot password

        div(style='padding:50px;')
            form(name="login", action="/apis/login", method="post")
                h1 Login
                div
                    p
                        label(style='padding-right:50px') Username or e-mail
                        input(type="text", name="user", placeholder='username@example.com')
                    p
                        label(style='padding-right:110px')  password
                        input(type="password", name="passwd")
                p
                    span
                        a(href='/users/forget', style='padding-right:175px') Forgot password
                    input(type="submit", name="submit", value="login")

2.修改routes/user.js

...

router.get('/signin', function(req, res, next) {
    if (req.session.logined) {
        res.redirect('/');
        return;
    }
    res.render('users/signin');
});

使用者登出頁面

1.修改routes/user.js
當登出之後會回到首頁.

...
router.get('/signout', function(req, res, next) {
    req.session.logined = false;
    res.redirect('/');
    res.end();
});

忘記密碼頁面

1.views/users/forget/jade

doctype html
html
    head
        title= 'Forget Password'
    body
        div(style='float:left;margin:10px;')
            a(href='/') Home

        div(style='float:right')
                a(href='/users/register', style='margin:10px;') Register
                a(href='/users/signin', style='margin:10px;') Log In

        div(style='padding:50px;')
            form(name="login", action="/apis/login", method="post")
                h1 Forget Password
                div
                    p
                        label(style='padding-right:50px') Username or e-mail
                        input(type="text", name="user", placeholder='username@example.com')
                p
                    span(style='padding-right:210px')
                    input(type="submit", name="submit", value="Forget Password")

2.修改routes/user.js

...

router.get('/forget', function(req, res, next) {
    if (req.session.logined) {
        res.redirect('/');
        return;
    }
    res.render('users/forget');
});