资料搜集

This commit is contained in:
yinkanglong_lab
2020-10-13 18:42:38 +08:00
parent df32365b3f
commit 8cd78fcbbc
148 changed files with 50907 additions and 4 deletions

View File

@@ -1,6 +1,48 @@
主要任务:
1. 完成吴恩达课程和作业(编程实现)
2. 完成机器学习基石(笔记+编程)
1. 完成吴恩达课程√和作业(编程实现)
2. 完成李宏毅机器学习(笔记+编程)
3. 完成统计学习方法(笔记+编程,熟悉numpy和matplotlib)
4. 复习线性代数(3blue1brown)和数理统计(一门课程,重新做笔记)
4. 复习线性代数(3blue1brown)和数理统计(一门课程,重新做笔记)
---------------------------------
时间安排
上午、下午:看视频
晚上:写代码
--------------------------------
周二:
李宏毅前三周的内容
吴恩达 作业和代码
周三:
李宏毅4周
吴恩达 作业代码
周四:
李宏毅5、6周
李宏毅 作业代码
周五:
李宏毅7周
李宏毅作业代码
周六:
李宏毅8周
李宏毅作业代码
周日:
李宏毅作业代码
------------------------------
后续安排:
下周主要任务:
李红统计学习方法+周志华西瓜书。
下下周主要任务
实践真正的机器学习项目+看论文,写开题报告。

View File

@@ -29,7 +29,7 @@ python3-numpy-scipy-matplotlib-pandas√
* 完成第一轮,笔记对照(看一部分,总结一部分。)
* 看吴恩达的PPT重新整理所有的笔记。
* 寻找吴恩达课程的课后题,使用python完成课后题
2. 《机器学习基石》上&下//李宏毅的机器学习也行
2. ~~《机器学习基石》上&下~~//李宏毅的机器学习也行
* 完成第二轮,笔记对照(跟上实践)
3. 《统计学习方法》一看。3Blue1Brown的视频看一看。跟着做做笔记。
4. 《机器学习西瓜书》一看。跟着做做笔记。

View File

@@ -0,0 +1 @@
.ipynb_checkpoints

View File

@@ -0,0 +1,97 @@
6.1101,17.592
5.5277,9.1302
8.5186,13.662
7.0032,11.854
5.8598,6.8233
8.3829,11.886
7.4764,4.3483
8.5781,12
6.4862,6.5987
5.0546,3.8166
5.7107,3.2522
14.164,15.505
5.734,3.1551
8.4084,7.2258
5.6407,0.71618
5.3794,3.5129
6.3654,5.3048
5.1301,0.56077
6.4296,3.6518
7.0708,5.3893
6.1891,3.1386
20.27,21.767
5.4901,4.263
6.3261,5.1875
5.5649,3.0825
18.945,22.638
12.828,13.501
10.957,7.0467
13.176,14.692
22.203,24.147
5.2524,-1.22
6.5894,5.9966
9.2482,12.134
5.8918,1.8495
8.2111,6.5426
7.9334,4.5623
8.0959,4.1164
5.6063,3.3928
12.836,10.117
6.3534,5.4974
5.4069,0.55657
6.8825,3.9115
11.708,5.3854
5.7737,2.4406
7.8247,6.7318
7.0931,1.0463
5.0702,5.1337
5.8014,1.844
11.7,8.0043
5.5416,1.0179
7.5402,6.7504
5.3077,1.8396
7.4239,4.2885
7.6031,4.9981
6.3328,1.4233
6.3589,-1.4211
6.2742,2.4756
5.6397,4.6042
9.3102,3.9624
9.4536,5.4141
8.8254,5.1694
5.1793,-0.74279
21.279,17.929
14.908,12.054
18.959,17.054
7.2182,4.8852
8.2951,5.7442
10.236,7.7754
5.4994,1.0173
20.341,20.992
10.136,6.6799
7.3345,4.0259
6.0062,1.2784
7.2259,3.3411
5.0269,-2.6807
6.5479,0.29678
7.5386,3.8845
5.0365,5.7014
10.274,6.7526
5.1077,2.0576
5.7292,0.47953
5.1884,0.20421
6.3557,0.67861
9.7687,7.5435
6.5159,5.3436
8.5172,4.2415
9.1802,6.7981
6.002,0.92695
5.5204,0.152
5.0594,2.8214
5.7077,1.8451
7.6366,4.2959
5.8707,7.2029
5.3054,1.9869
8.2934,0.14454
13.394,9.0551
5.4369,0.61705

View File

@@ -0,0 +1,47 @@
2104,3,399900
1600,3,329900
2400,3,369000
1416,2,232000
3000,4,539900
1985,4,299900
1534,3,314900
1427,3,198999
1380,3,212000
1494,3,242500
1940,4,239999
2000,3,347000
1890,3,329999
4478,5,699900
1268,3,259900
2300,4,449900
1320,2,299900
1236,3,199900
2609,4,499998
3031,4,599000
1767,3,252900
1888,2,255000
1604,3,242900
1962,4,259900
3890,3,573900
1100,3,249900
1458,3,464500
2526,3,469000
2200,3,475000
2637,3,299900
1839,2,349900
1000,1,169900
2040,4,314900
3137,3,579900
1811,4,285900
1437,3,249900
1239,3,229900
2132,4,345000
4215,4,549000
2162,4,287000
1664,2,368500
2238,3,329900
2567,4,314000
1200,3,299000
852,2,179900
1852,4,299900
1203,3,239500

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,100 @@
34.62365962451697,78.0246928153624,0
30.28671076822607,43.89499752400101,0
35.84740876993872,72.90219802708364,0
60.18259938620976,86.30855209546826,1
79.0327360507101,75.3443764369103,1
45.08327747668339,56.3163717815305,0
61.10666453684766,96.51142588489624,1
75.02474556738889,46.55401354116538,1
76.09878670226257,87.42056971926803,1
84.43281996120035,43.53339331072109,1
95.86155507093572,38.22527805795094,0
75.01365838958247,30.60326323428011,0
82.30705337399482,76.48196330235604,1
69.36458875970939,97.71869196188608,1
39.53833914367223,76.03681085115882,0
53.9710521485623,89.20735013750205,1
69.07014406283025,52.74046973016765,1
67.94685547711617,46.67857410673128,0
70.66150955499435,92.92713789364831,1
76.97878372747498,47.57596364975532,1
67.37202754570876,42.83843832029179,0
89.67677575072079,65.79936592745237,1
50.534788289883,48.85581152764205,0
34.21206097786789,44.20952859866288,0
77.9240914545704,68.9723599933059,1
62.27101367004632,69.95445795447587,1
80.1901807509566,44.82162893218353,1
93.114388797442,38.80067033713209,0
61.83020602312595,50.25610789244621,0
38.78580379679423,64.99568095539578,0
61.379289447425,72.80788731317097,1
85.40451939411645,57.05198397627122,1
52.10797973193984,63.12762376881715,0
52.04540476831827,69.43286012045222,1
40.23689373545111,71.16774802184875,0
54.63510555424817,52.21388588061123,0
33.91550010906887,98.86943574220611,0
64.17698887494485,80.90806058670817,1
74.78925295941542,41.57341522824434,0
34.1836400264419,75.2377203360134,0
83.90239366249155,56.30804621605327,1
51.54772026906181,46.85629026349976,0
94.44336776917852,65.56892160559052,1
82.36875375713919,40.61825515970618,0
51.04775177128865,45.82270145776001,0
62.22267576120188,52.06099194836679,0
77.19303492601364,70.45820000180959,1
97.77159928000232,86.7278223300282,1
62.07306379667647,96.76882412413983,1
91.56497449807442,88.69629254546599,1
79.94481794066932,74.16311935043758,1
99.2725269292572,60.99903099844988,1
90.54671411399852,43.39060180650027,1
34.52451385320009,60.39634245837173,0
50.2864961189907,49.80453881323059,0
49.58667721632031,59.80895099453265,0
97.64563396007767,68.86157272420604,1
32.57720016809309,95.59854761387875,0
74.24869136721598,69.82457122657193,1
71.79646205863379,78.45356224515052,1
75.3956114656803,85.75993667331619,1
35.28611281526193,47.02051394723416,0
56.25381749711624,39.26147251058019,0
30.05882244669796,49.59297386723685,0
44.66826172480893,66.45008614558913,0
66.56089447242954,41.09209807936973,0
40.45755098375164,97.53518548909936,1
49.07256321908844,51.88321182073966,0
80.27957401466998,92.11606081344084,1
66.74671856944039,60.99139402740988,1
32.72283304060323,43.30717306430063,0
64.0393204150601,78.03168802018232,1
72.34649422579923,96.22759296761404,1
60.45788573918959,73.09499809758037,1
58.84095621726802,75.85844831279042,1
99.82785779692128,72.36925193383885,1
47.26426910848174,88.47586499559782,1
50.45815980285988,75.80985952982456,1
60.45555629271532,42.50840943572217,0
82.22666157785568,42.71987853716458,0
88.9138964166533,69.80378889835472,1
94.83450672430196,45.69430680250754,1
67.31925746917527,66.58935317747915,1
57.23870631569862,59.51428198012956,1
80.36675600171273,90.96014789746954,1
68.46852178591112,85.59430710452014,1
42.0754545384731,78.84478600148043,0
75.47770200533905,90.42453899753964,1
78.63542434898018,96.64742716885644,1
52.34800398794107,60.76950525602592,0
94.09433112516793,77.15910509073893,1
90.44855097096364,87.50879176484702,1
55.48216114069585,35.57070347228866,0
74.49269241843041,84.84513684930135,1
89.84580670720979,45.35828361091658,1
83.48916274498238,48.38028579728175,1
42.2617008099817,87.10385094025457,1
99.31500880510394,68.77540947206617,1
55.34001756003703,64.9319380069486,1
74.77589300092767,89.52981289513276,1

View File

@@ -0,0 +1,118 @@
0.051267,0.69956,1
-0.092742,0.68494,1
-0.21371,0.69225,1
-0.375,0.50219,1
-0.51325,0.46564,1
-0.52477,0.2098,1
-0.39804,0.034357,1
-0.30588,-0.19225,1
0.016705,-0.40424,1
0.13191,-0.51389,1
0.38537,-0.56506,1
0.52938,-0.5212,1
0.63882,-0.24342,1
0.73675,-0.18494,1
0.54666,0.48757,1
0.322,0.5826,1
0.16647,0.53874,1
-0.046659,0.81652,1
-0.17339,0.69956,1
-0.47869,0.63377,1
-0.60541,0.59722,1
-0.62846,0.33406,1
-0.59389,0.005117,1
-0.42108,-0.27266,1
-0.11578,-0.39693,1
0.20104,-0.60161,1
0.46601,-0.53582,1
0.67339,-0.53582,1
-0.13882,0.54605,1
-0.29435,0.77997,1
-0.26555,0.96272,1
-0.16187,0.8019,1
-0.17339,0.64839,1
-0.28283,0.47295,1
-0.36348,0.31213,1
-0.30012,0.027047,1
-0.23675,-0.21418,1
-0.06394,-0.18494,1
0.062788,-0.16301,1
0.22984,-0.41155,1
0.2932,-0.2288,1
0.48329,-0.18494,1
0.64459,-0.14108,1
0.46025,0.012427,1
0.6273,0.15863,1
0.57546,0.26827,1
0.72523,0.44371,1
0.22408,0.52412,1
0.44297,0.67032,1
0.322,0.69225,1
0.13767,0.57529,1
-0.0063364,0.39985,1
-0.092742,0.55336,1
-0.20795,0.35599,1
-0.20795,0.17325,1
-0.43836,0.21711,1
-0.21947,-0.016813,1
-0.13882,-0.27266,1
0.18376,0.93348,0
0.22408,0.77997,0
0.29896,0.61915,0
0.50634,0.75804,0
0.61578,0.7288,0
0.60426,0.59722,0
0.76555,0.50219,0
0.92684,0.3633,0
0.82316,0.27558,0
0.96141,0.085526,0
0.93836,0.012427,0
0.86348,-0.082602,0
0.89804,-0.20687,0
0.85196,-0.36769,0
0.82892,-0.5212,0
0.79435,-0.55775,0
0.59274,-0.7405,0
0.51786,-0.5943,0
0.46601,-0.41886,0
0.35081,-0.57968,0
0.28744,-0.76974,0
0.085829,-0.75512,0
0.14919,-0.57968,0
-0.13306,-0.4481,0
-0.40956,-0.41155,0
-0.39228,-0.25804,0
-0.74366,-0.25804,0
-0.69758,0.041667,0
-0.75518,0.2902,0
-0.69758,0.68494,0
-0.4038,0.70687,0
-0.38076,0.91886,0
-0.50749,0.90424,0
-0.54781,0.70687,0
0.10311,0.77997,0
0.057028,0.91886,0
-0.10426,0.99196,0
-0.081221,1.1089,0
0.28744,1.087,0
0.39689,0.82383,0
0.63882,0.88962,0
0.82316,0.66301,0
0.67339,0.64108,0
1.0709,0.10015,0
-0.046659,-0.57968,0
-0.23675,-0.63816,0
-0.15035,-0.36769,0
-0.49021,-0.3019,0
-0.46717,-0.13377,0
-0.28859,-0.060673,0
-0.61118,-0.067982,0
-0.66302,-0.21418,0
-0.59965,-0.41886,0
-0.72638,-0.082602,0
-0.83007,0.31213,0
-0.72062,0.53874,0
-0.59389,0.49488,0
-0.48445,0.99927,0
-0.0063364,0.99927,0
0.63265,-0.030612,0

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
../../ex3/data/ex3data1.mat

View File

@@ -0,0 +1 @@
../../ex3/data/ex3weights.mat

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,10 @@
> Anyone knows how much it costs to host a web portal ?
>
Well, it depends on how many visitors you're expecting.
This can be anywhere from less than 10 bucks a month to a couple of $100.
You should checkout http://www.rackspace.com/ or perhaps Amazon EC2
if youre running something big..
To unsubscribe yourself from this mailing list, send an email to:
groupname-unsubscribe@egroups.com

View File

@@ -0,0 +1,34 @@
Folks,
my first time posting - have a bit of Unix experience, but am new to Linux.
Just got a new PC at home - Dell box with Windows XP. Added a second hard disk
for Linux. Partitioned the disk and have installed Suse 7.2 from CD, which went
fine except it didn't pick up my monitor.
I have a Dell branded E151FPp 15" LCD flat panel monitor and a nVidia GeForce4
Ti4200 video card, both of which are probably too new to feature in Suse's default
set. I downloaded a driver from the nVidia website and installed it using RPM.
Then I ran Sax2 (as was recommended in some postings I found on the net), but
it still doesn't feature my video card in the available list. What next?
Another problem. I have a Dell branded keyboard and if I hit Caps-Lock twice,
the whole machine crashes (in Linux, not Windows) - even the on/off switch is
inactive, leaving me to reach for the power cable instead.
If anyone can help me in any way with these probs., I'd be really grateful -
I've searched the 'net but have run out of ideas.
Or should I be going for a different version of Linux such as RedHat? Opinions
welcome.
Thanks a lot,
Peter
--
Irish Linux Users' Group: ilug@linux.ie
http://www.linux.ie/mailman/listinfo/ilug for (un)subscription information.
List maintainer: listmaster@linux.ie

View File

@@ -0,0 +1,42 @@
Do You Want To Make $1000 Or More Per Week?
If you are a motivated and qualified individual - I
will personally demonstrate to you a system that will
make you $1,000 per week or more! This is NOT mlm.
Call our 24 hour pre-recorded number to get the
details.
000-456-789
I need people who want to make serious money. Make
the call and get the facts.
Invest 2 minutes in yourself now!
000-456-789
Looking forward to your call and I will introduce you
to people like yourself who
are currently making $10,000 plus per week!
000-456-789
3484lJGv6-241lEaN9080lRmS6-271WxHo7524qiyT5-438rjUv5615hQcf0-662eiDB9057dMtVl72

View File

@@ -0,0 +1,8 @@
Best Buy Viagra Generic Online
Viagra 100mg x 60 Pills $125, Free Pills & Reorder Discount, Top Selling 100% Quality & Satisfaction guaranteed!
We accept VISA, Master & E-Check Payments, 90000+ Satisfied Customers!
http://medphysitcstech.ru

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,488 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Programming Exercise 6: Support Vector Machines"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import scipy.io #Used to load the OCTAVE *.mat files\n",
"from sklearn import svm #SVM software\n",
"import re #regular expression for e-mail processing\n",
"\n",
"# This is one possible porter stemmer \n",
"# (note: I had to do a pip install stemming)\n",
"# https://pypi.python.org/pypi/stemming/1.0\n",
"from stemming.porter2 import stem\n",
"\n",
"# This porter stemmer seems to more accurately duplicate the\n",
"# porter stemmer used in the OCTAVE assignment code\n",
"# (note: I had to do a pip install nltk)\n",
"# I'll note that both stemmers have very similar results\n",
"import nltk, nltk.stem.porter"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 2 Spam Classification"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 2.1 Preprocessing Emails"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"emailSample1.txt:\n",
"> Anyone knows how much it costs to host a web portal ?\r\n",
">\r\n",
"Well, it depends on how many visitors you're expecting.\r\n",
"This can be anywhere from less than 10 bucks a month to a couple of $100. \r\n",
"You should checkout http://www.rackspace.com/ or perhaps Amazon EC2 \r\n",
"if youre running something big..\r\n",
"\r\n",
"To unsubscribe yourself from this mailing list, send an email to:\r\n",
"groupname-unsubscribe@egroups.com\r\n",
"\r\n"
]
}
],
"source": [
"print \"emailSample1.txt:\"\n",
"!cat data/emailSample1.txt"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def preProcess( email ):\n",
" \"\"\"\n",
" Function to do some pre processing (simplification of e-mails).\n",
" Comments throughout implementation describe what it does.\n",
" Input = raw e-mail\n",
" Output = processed (simplified) email\n",
" \"\"\"\n",
" # Make the entire e-mail lower case\n",
" email = email.lower()\n",
" \n",
" # Strip html tags (strings that look like <blah> where 'blah' does not\n",
" # contain '<' or '>')... replace with a space\n",
" email = re.sub('<[^<>]+>', ' ', email);\n",
" \n",
" #Any numbers get replaced with the string 'number'\n",
" email = re.sub('[0-9]+', 'number', email)\n",
" \n",
" #Anything starting with http or https:// replaced with 'httpaddr'\n",
" email = re.sub('(http|https)://[^\\s]*', 'httpaddr', email)\n",
" \n",
" #Strings with \"@\" in the middle are considered emails --> 'emailaddr'\n",
" email = re.sub('[^\\s]+@[^\\s]+', 'emailaddr', email);\n",
" \n",
" #The '$' sign gets replaced with 'dollar'\n",
" email = re.sub('[$]+', 'dollar', email);\n",
" \n",
" return email"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def email2TokenList( raw_email ):\n",
" \"\"\"\n",
" Function that takes in preprocessed (simplified) email, tokenizes it,\n",
" stems each word, and returns an (ordered) list of tokens in the e-mail\n",
" \"\"\"\n",
" \n",
" # I'll use the NLTK stemmer because it more accurately duplicates the\n",
" # performance of the OCTAVE implementation in the assignment\n",
" stemmer = nltk.stem.porter.PorterStemmer()\n",
" \n",
" email = preProcess( raw_email )\n",
"\n",
" #Split the e-mail into individual words (tokens) (split by the delimiter ' ')\n",
" #but also split by delimiters '@', '$', '/', etc etc\n",
" #Splitting by many delimiters is easiest with re.split()\n",
" tokens = re.split('[ \\@\\$\\/\\#\\.\\-\\:\\&\\*\\+\\=\\[\\]\\?\\!\\(\\)\\{\\}\\,\\'\\\"\\>\\_\\<\\;\\%]', email)\n",
" \n",
" #Loop over each word (token) and use a stemmer to shorten it,\n",
" #then check if the word is in the vocab_list... if it is,\n",
" #store what index in the vocab_list the word is\n",
" tokenlist = []\n",
" for token in tokens:\n",
" \n",
" #Remove any non alphanumeric characters\n",
" token = re.sub('[^a-zA-Z0-9]', '', token);\n",
"\n",
" #Use the Porter stemmer to stem the word\n",
" stemmed = stemmer.stem( token )\n",
" \n",
" #Throw out empty tokens\n",
" if not len(token): continue\n",
" \n",
" #Store a list of all unique stemmed words\n",
" tokenlist.append(stemmed)\n",
" \n",
" return tokenlist"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"##### 2.1.1 Vocabulary List"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def getVocabDict(reverse=False):\n",
" \"\"\"\n",
" Function to read in the supplied vocab list text file into a dictionary.\n",
" I'll use this for now, but since I'm using a slightly different stemmer,\n",
" I'd like to generate this list myself from some sort of data set...\n",
" Dictionary key is the stemmed word, value is the index in the text file\n",
" If \"reverse\", the keys and values are switched.\n",
" \"\"\"\n",
" vocab_dict = {}\n",
" with open(\"data/vocab.txt\") as f:\n",
" for line in f:\n",
" (val, key) = line.split()\n",
" if not reverse:\n",
" vocab_dict[key] = int(val)\n",
" else:\n",
" vocab_dict[int(val)] = key\n",
" \n",
" return vocab_dict"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def email2VocabIndices( raw_email, vocab_dict ):\n",
" \"\"\"\n",
" Function that takes in a raw email and returns a list of indices corresponding\n",
" to the location in vocab_dict for each stemmed word in the email.\n",
" \"\"\"\n",
" tokenlist = email2TokenList( raw_email )\n",
" index_list = [ vocab_dict[token] for token in tokenlist if token in vocab_dict ]\n",
" return index_list"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 2.2 Extracting Features from Emails"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def email2FeatureVector( raw_email, vocab_dict ):\n",
" \"\"\"\n",
" Function that takes as input a raw email, and returns a vector of shape\n",
" (n,1) where n is the size of the vocab_dict.\n",
" The first element in this vector is 1 if the vocab word with index == 1\n",
" is in the raw_email, 0 otherwise.\n",
" \"\"\"\n",
" n = len(vocab_dict)\n",
" result = np.zeros((n,1))\n",
" vocab_indices = email2VocabIndices( email_contents, vocab_dict )\n",
" for idx in vocab_indices:\n",
" result[idx] = 1\n",
" return result"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Length of feature vector is 1899\n",
"Number of non-zero entries is: 45\n"
]
}
],
"source": [
"# \" ... run your code on the email sample. You should see that the feature vector \n",
"# has length 1899 and 45 non-zero entries.\"\n",
"\n",
"vocab_dict = getVocabDict()\n",
"email_contents = open( 'data/emailSample1.txt', 'r' ).read()\n",
"test_fv = email2FeatureVector( email_contents, vocab_dict )\n",
"\n",
"print \"Length of feature vector is %d\" % len(test_fv)\n",
"print \"Number of non-zero entries is: %d\" % sum(test_fv==1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 2.3 Training SVM for Spam Classification"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Read in the training set and test set provided\n",
"# Note the feature vectors correspond to the stemming implementation\n",
"# done in the OCTAVE code... which may be different than mine.\n",
"\n",
"# Training set\n",
"datafile = 'data/spamTrain.mat'\n",
"mat = scipy.io.loadmat( datafile )\n",
"X, y = mat['X'], mat['y']\n",
"#NOT inserting a column of 1's in case SVM software does it for me automatically...\n",
"#X = np.insert(X ,0,1,axis=1)\n",
"\n",
"# Test set\n",
"datafile = 'data/spamTest.mat'\n",
"mat = scipy.io.loadmat( datafile )\n",
"Xtest, ytest = mat['Xtest'], mat['ytest']"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Total number of training emails = 4000\n",
"Number of training spam emails = 1277\n",
"Number of training nonspam emails = 2723\n"
]
}
],
"source": [
"pos = np.array([X[i] for i in xrange(X.shape[0]) if y[i] == 1])\n",
"neg = np.array([X[i] for i in xrange(X.shape[0]) if y[i] == 0])\n",
"print 'Total number of training emails = ',X.shape[0]\n",
"print 'Number of training spam emails = ',pos.shape[0]\n",
"print 'Number of training nonspam emails = ',neg.shape[0]"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"SVC(C=0.1, cache_size=200, class_weight=None, coef0=0.0, degree=3, gamma=0.0,\n",
" kernel='linear', max_iter=-1, probability=False, random_state=None,\n",
" shrinking=True, tol=0.001, verbose=False)"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Run the SVM training (with C = 0.1) using SVM software. \n",
"\n",
"# First we make an instance of an SVM with C=0.1 and 'linear' kernel\n",
"linear_svm = svm.SVC(C=0.1, kernel='linear')\n",
"\n",
"# Now we fit the SVM to our X matrix, given the labels y\n",
"linear_svm.fit( X, y.flatten() )"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Training accuracy = 99.83%\n",
"Test set accuracy = 98.90%\n"
]
}
],
"source": [
"# \"Once the training completes, you should see that the classifier gets a \n",
"# training accuracy of about 99.8% and a test accuracy of about 98.5%\"\n",
"\n",
"train_predictions = linear_svm.predict(X).reshape((y.shape[0],1))\n",
"train_acc = 100. * float(sum(train_predictions == y))/y.shape[0]\n",
"print 'Training accuracy = %0.2f%%' % train_acc\n",
"\n",
"test_predictions = linear_svm.predict(Xtest).reshape((ytest.shape[0],1))\n",
"test_acc = 100. * float(sum(test_predictions == ytest))/ytest.shape[0]\n",
"print 'Test set accuracy = %0.2f%%' % test_acc"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 2.4 Top Predictors for Spam"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"collapsed": false,
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The 15 most important words to classify a spam e-mail are:\n",
"['otherwis', 'clearli', 'remot', 'gt', 'visa', 'base', 'doesn', 'wife', 'previous', 'player', 'mortgag', 'natur', 'll', 'futur', 'hot']\n",
"\n",
"The 15 least important words to classify a spam e-mail are:\n",
"['http', 'toll', 'xp', 'ratio', 'august', 'unsubscrib', 'useless', 'numberth', 'round', 'linux', 'datapow', 'wrong', 'urgent', 'that', 'spam']\n",
"\n",
"# of spam containing \"otherwis\" = 804/1277 = 62.96%\n",
"# of NON spam containing \"otherwis\" = 301/2723 = 11.05%\n"
]
}
],
"source": [
"# Determine the words most likely to indicate an e-mail is a spam\n",
"# From the trained SVM we can get a list of the weight coefficients for each\n",
"# word (technically, each word index)\n",
"\n",
"vocab_dict_flipped = getVocabDict(reverse=True)\n",
"\n",
"#Sort indicies from most important to least-important (high to low weight)\n",
"sorted_indices = np.argsort( linear_svm.coef_, axis=None )[::-1]\n",
"print \"The 15 most important words to classify a spam e-mail are:\"\n",
"print [ vocab_dict_flipped[x] for x in sorted_indices[:15] ]\n",
"print\n",
"print \"The 15 least important words to classify a spam e-mail are:\"\n",
"print [ vocab_dict_flipped[x] for x in sorted_indices[-15:] ]\n",
"print\n",
"\n",
"# Most common word (mostly to debug):\n",
"most_common_word = vocab_dict_flipped[sorted_indices[0]]\n",
"print '# of spam containing \\\"%s\\\" = %d/%d = %0.2f%%'% \\\n",
" (most_common_word, sum(pos[:,1190]),pos.shape[0], \\\n",
" 100.*float(sum(pos[:,1190]))/pos.shape[0])\n",
"print '# of NON spam containing \\\"%s\\\" = %d/%d = %0.2f%%'% \\\n",
" (most_common_word, sum(neg[:,1190]),neg.shape[0], \\\n",
" 100.*float(sum(neg[:,1190]))/neg.shape[0])"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Note my SVM gets some different predictor words for spam than shown in the\n",
"# assignment PDF... I've done debugging and I'm confident it's due to a different\n",
"# SVM software package, not because of a bug or something in my code.\n",
"\n",
"# Also note the optional exercises \"Try your own emails\" and \"Build your own\n",
"# dataset\" I will be doing seperately in a blog post... Check out\n",
"# blog.davidkaleko.com/svm-email-filter-implementation.html to have a look!"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.5"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,102 @@
import numpy as np
import matplotlib.pylab as plt
from mpl_toolkits.mplot3d import Axes3D
# ==================== Part 1: Basic Function ====================
print('Running warmUpExercise...')
print('5x5 Identity Matrix: ')
A = np.eye(5)
print(A)
_ = input('Press [Enter] to continue.')
# ======================= Part 2: Plotting =======================
# 绘制散点图
def plotData(x, y):
plt.plot(x, y, 'rx', ms=10)
plt.xlabel('Population of City in 10,000')
plt.ylabel('Profit in $10,000')
plt.show()
print('Plotting Data...')
data = np.loadtxt('ex1data1.txt', delimiter=',')
X = data[:, 0]; Y = data[:, 1]
m = np.size(Y, 0)
plotData(X, Y)
_ = input('Press [Enter] to continue.')
# =================== Part 3: Gradient descent ===================
# 计算损失函数值
def computeCost(x, y, theta):
ly = np.size(y, 0)
cost = (x.dot(theta)-y).dot(x.dot(theta)-y)/(2*ly)
return cost
# 迭代计算theta
def gradientDescent(x, y, theta, alpha, num_iters):
m = np.size(y, 0)
j_history = np.zeros((num_iters,))
for i in range(num_iters):
deltaJ = x.T.dot(x.dot(theta)-y)/m
theta = theta-alpha*deltaJ
j_history[i] = computeCost(x, y, theta)
return theta, j_history
print('Running Gradient Descent ...')
X = np.vstack((np.ones((m,)), X)).T
theta = np.zeros((2,)) # 初始化参数
iterations = 1500
alpha = 0.01
J = computeCost(X, Y, theta)
print(J)
theta, j_history = gradientDescent(X, Y, theta, alpha, iterations)
print('Theta found by gradient descent: ', theta)
plt.plot(X[:, 1], Y, 'rx', ms=10, label='Training data')
plt.plot(X[:, 1], X.dot(theta), '-', label='Linear regression')
plt.xlabel('Population of City in 10,000')
plt.ylabel('Profit in $10,000')
plt.legend(loc='upper right')
plt.show()
# Predict values for population sizes of 35,000 and 70,000
predict1 = np.array([1, 3.5]).dot(theta)
print('For population = 35,000, we predict a profit of ', predict1*10000)
predict2 = np.array([1, 7.0]).dot(theta)
print('For population = 70,000, we predict a profit of ', predict2*10000)
_ = input('Press [Enter] to continue.')
# ============= Part 4: Visualizing J(theta_0, theta_1) =============
print('Visualizing J(theta_0, theta_1) ...')
theta0_vals = np.linspace(-10, 10, 100)
theta1_vals = np.linspace(-1, 4, 100)
J_vals = np.zeros((np.size(theta0_vals, 0), np.size(theta1_vals, 0)))
for i in range(np.size(theta0_vals, 0)):
for j in range(np.size(theta1_vals, 0)):
t = np.array([theta0_vals[i], theta1_vals[j]])
J_vals[i, j] = computeCost(X, Y, t)
# 绘制三维图像
theta0_vals, theta1_vals = np.meshgrid(theta0_vals, theta1_vals)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_surface(theta0_vals, theta1_vals, J_vals.T)
ax.set_xlabel(r'$\theta$0')
ax.set_ylabel(r'$\theta$1')
# 绘制等高线图
fig2 = plt.figure()
ax2 = fig2.add_subplot(111)
ax2.contour(theta0_vals, theta1_vals, J_vals.T, np.logspace(-2, 3, 20))
ax2.plot(theta[0], theta[1], 'rx', ms=10, lw=2)
ax2.set_xlabel(r'$\theta$0')
ax2.set_ylabel(r'$\theta$1')
plt.show()

View File

@@ -0,0 +1,89 @@
import numpy as np
import matplotlib.pylab as plt
from scipy import linalg
# ================ Part 1: Feature Normalization ================
print('Loading data ...')
data = np.loadtxt('ex1data2.txt', delimiter=',')
X = data[:, 0:2]
Y = data[:, 2]
m = np.size(Y, 0)
print('First 10 examples from the dataset:')
for i in range(10):
print('x=[%.0f %.0f], y=%.0f' %(X[i, 0], X[i, 1], Y[i]))
_ = input('Press [Enter] to continue.')
print('Normalizing Features...')
# 归一化函数
def featureNormalize(x):
mu = np.mean(x, axis=0)
sigma = np.std(x, axis=0)
x_norm = np.divide(x-mu, sigma)
return x_norm, mu, sigma
X, mu, sigma = featureNormalize(X)
X = np.concatenate((np.ones((m, 1)), X), axis=1)
# ================ Part 2: Gradient Descent ================
print('Running gradient descent ...')
# 计算损失函数值
def computeCostMulti(x, y, theta):
m = np.size(y, 0)
j = (x.dot(theta)-y).dot(x.dot(theta)-y)/(2*m)
return j
def gradientDescentMulti(x, y, theta, alpha, num_iters):
m = np.size(y, 0)
j_history = np.zeros((num_iters,))
for i in range(num_iters):
theta = theta-alpha*(X.T.dot(X.dot(theta)-y)/m)
j_history[i] = computeCostMulti(x, y, theta)
return theta, j_history
alpha = 0.01
num_iters = 400
theta = np.zeros((3,))
theta, j_history = gradientDescentMulti(X, Y, theta, alpha, num_iters)
plt.plot(np.arange(np.size(j_history, 0)), j_history, '-b', lw=2)
plt.xlabel('Number of iterations')
plt.ylabel('Cost J')
plt.show()
print('Theta computed from gradient descent: ', theta)
# Estimate the price of a 1650 sq-ft, 3 br house
X_test = np.array([1650, 3])
X_test = np.divide(X_test-mu, sigma)
X_test = np.hstack((1, X_test))
price = X_test.dot(theta)
print('Predicted price of a 1650 sq-ft, 3 br house (using gradient descent): ', price)
_ = input('Press [Enter] to continue.')
# ================ Part 3: Normal Equations ================
data = np.loadtxt('ex1data2.txt', delimiter=',')
X = data[:, 0:2]
Y = data[:, 2]
m = np.size(Y, 0)
X = np.concatenate((np.ones((m, 1)), X), axis=1)
# 利用标准公式求解theta
def normalEqn(x, y):
theta = linalg.pinv(X.T.dot(X)).dot(X.T).dot(y)
return theta
theta = normalEqn(X, Y)
print('Theta computed from the normal equations: ', theta)
# Estimate the price of a 1650 sq-ft, 3 br house
X_test = np.array([1, 1650, 3])
price = X_test.dot(theta)
print('Predicted price of a 1650 sq-ft, 3 br house (using normal equations): ', price)

View File

@@ -0,0 +1,97 @@
6.1101,17.592
5.5277,9.1302
8.5186,13.662
7.0032,11.854
5.8598,6.8233
8.3829,11.886
7.4764,4.3483
8.5781,12
6.4862,6.5987
5.0546,3.8166
5.7107,3.2522
14.164,15.505
5.734,3.1551
8.4084,7.2258
5.6407,0.71618
5.3794,3.5129
6.3654,5.3048
5.1301,0.56077
6.4296,3.6518
7.0708,5.3893
6.1891,3.1386
20.27,21.767
5.4901,4.263
6.3261,5.1875
5.5649,3.0825
18.945,22.638
12.828,13.501
10.957,7.0467
13.176,14.692
22.203,24.147
5.2524,-1.22
6.5894,5.9966
9.2482,12.134
5.8918,1.8495
8.2111,6.5426
7.9334,4.5623
8.0959,4.1164
5.6063,3.3928
12.836,10.117
6.3534,5.4974
5.4069,0.55657
6.8825,3.9115
11.708,5.3854
5.7737,2.4406
7.8247,6.7318
7.0931,1.0463
5.0702,5.1337
5.8014,1.844
11.7,8.0043
5.5416,1.0179
7.5402,6.7504
5.3077,1.8396
7.4239,4.2885
7.6031,4.9981
6.3328,1.4233
6.3589,-1.4211
6.2742,2.4756
5.6397,4.6042
9.3102,3.9624
9.4536,5.4141
8.8254,5.1694
5.1793,-0.74279
21.279,17.929
14.908,12.054
18.959,17.054
7.2182,4.8852
8.2951,5.7442
10.236,7.7754
5.4994,1.0173
20.341,20.992
10.136,6.6799
7.3345,4.0259
6.0062,1.2784
7.2259,3.3411
5.0269,-2.6807
6.5479,0.29678
7.5386,3.8845
5.0365,5.7014
10.274,6.7526
5.1077,2.0576
5.7292,0.47953
5.1884,0.20421
6.3557,0.67861
9.7687,7.5435
6.5159,5.3436
8.5172,4.2415
9.1802,6.7981
6.002,0.92695
5.5204,0.152
5.0594,2.8214
5.7077,1.8451
7.6366,4.2959
5.8707,7.2029
5.3054,1.9869
8.2934,0.14454
13.394,9.0551
5.4369,0.61705

View File

@@ -0,0 +1,47 @@
2104,3,399900
1600,3,329900
2400,3,369000
1416,2,232000
3000,4,539900
1985,4,299900
1534,3,314900
1427,3,198999
1380,3,212000
1494,3,242500
1940,4,239999
2000,3,347000
1890,3,329999
4478,5,699900
1268,3,259900
2300,4,449900
1320,2,299900
1236,3,199900
2609,4,499998
3031,4,599000
1767,3,252900
1888,2,255000
1604,3,242900
1962,4,259900
3890,3,573900
1100,3,249900
1458,3,464500
2526,3,469000
2200,3,475000
2637,3,299900
1839,2,349900
1000,1,169900
2040,4,314900
3137,3,579900
1811,4,285900
1437,3,249900
1239,3,229900
2132,4,345000
4215,4,549000
2162,4,287000
1664,2,368500
2238,3,329900
2567,4,314000
1200,3,299000
852,2,179900
1852,4,299900
1203,3,239500

View File

@@ -0,0 +1,99 @@
import numpy as np
import matplotlib.pylab as plt
import scipy.optimize as op
# Load Data
data = np.loadtxt('ex2data1.txt', delimiter=',')
X = data[:, 0:2]
Y = data[:, 2]
# ==================== Part 1: Plotting ====================
print('Plotting data with + indicating (y = 1) examples and o indicating (y = 0) examples.')
# 绘制散点图像
def plotData(x, y):
pos = np.where(y == 1)
neg = np.where(y == 0)
p1 = plt.scatter(x[pos, 0], x[pos, 1], marker='+', s=30, color='b')
p2 = plt.scatter(x[neg, 0], x[neg, 1], marker='o', s=30, color='y')
plt.legend((p1, p2), ('Admitted', 'Not admitted'), loc='upper right', fontsize=8)
plt.xlabel('Exam 1 score')
plt.ylabel('Exam 2 score')
plt.show()
plotData(X, Y)
_ = input('Press [Enter] to continue.')
# ============ Part 2: Compute Cost and Gradient ============
m, n = np.shape(X)
X = np.concatenate((np.ones((m, 1)), X), axis=1)
init_theta = np.zeros((n+1,))
# sigmoid函数
def sigmoid(z):
g = 1/(1+np.exp(-1*z))
return g
# 计算损失函数和梯度函数
def costFunction(theta, x, y):
m = np.size(y, 0)
h = sigmoid(x.dot(theta))
if np.sum(1-h < 1e-10) != 0:
return np.inf
j = -1/m*(y.dot(np.log(h))+(1-y).dot(np.log(1-h)))
return j
def gradFunction(theta, x, y):
m = np.size(y, 0)
grad = 1 / m * (x.T.dot(sigmoid(x.dot(theta)) - y))
return grad
cost = costFunction(init_theta, X, Y)
grad = gradFunction(init_theta, X, Y)
print('Cost at initial theta (zeros): ', cost)
print('Gradient at initial theta (zeros): ', grad)
_ = input('Press [Enter] to continue.')
# ============= Part 3: Optimizing using fmin_bfgs =============
# 注:此处与原始的情况有些出入
result = op.minimize(costFunction, x0=init_theta, method='BFGS', jac=gradFunction, args=(X, Y))
theta = result.x
print('Cost at theta found by fmin_bfgs: ', result.fun)
print('theta: ', theta)
# 绘制图像
def plotDecisionBoundary(theta, x, y):
pos = np.where(y == 1)
neg = np.where(y == 0)
p1 = plt.scatter(x[pos, 1], x[pos, 2], marker='+', s=60, color='r')
p2 = plt.scatter(x[neg, 1], x[neg, 2], marker='o', s=60, color='y')
plot_x = np.array([np.min(x[:, 1])-2, np.max(x[:, 1]+2)])
plot_y = -1/theta[2]*(theta[1]*plot_x+theta[0])
plt.plot(plot_x, plot_y)
plt.legend((p1, p2), ('Admitted', 'Not admitted'), loc='upper right', fontsize=8)
plt.xlabel('Exam 1 score')
plt.ylabel('Exam 2 score')
plt.show()
plotDecisionBoundary(theta, X, Y)
_ = input('Press [Enter] to continue.')
# ============== Part 4: Predict and Accuracies ==============
prob = sigmoid(np.array([1, 45, 85]).dot(theta))
print('For a student with scores 45 and 85, we predict an admission probability of: ', prob)
# 预测给定值
def predict(theta, x):
m = np.size(X, 0)
p = np.zeros((m,))
pos = np.where(x.dot(theta) >= 0)
neg = np.where(x.dot(theta) < 0)
p[pos] = 1
p[neg] = 0
return p
p = predict(theta, X)
print('Train Accuracy: ', np.sum(p == Y)/np.size(Y, 0))
_ = input('Press [Enter] to continue.')
p=predict(theta, X)

View File

@@ -0,0 +1,103 @@
import numpy as np
import matplotlib.pylab as plt
import scipy.optimize as op
# 加载数据
data = np.loadtxt('ex2data2.txt', delimiter=',')
X = data[:, 0:2]
Y = data[:, 2]
def plotData(x, y):
pos = np.where(y == 1)
neg = np.where(y == 0)
p1 = plt.scatter(x[pos, 0], x[pos, 1], marker='+', s=50, color='b')
p2 = plt.scatter(x[neg, 0], x[neg, 1], marker='o', s=50, color='y')
plt.legend((p1, p2), ('Admitted', 'Not admitted'), loc='upper right', fontsize=8)
plt.xlabel('Exam 1 score')
plt.ylabel('Exam 2 score')
plt.show()
plotData(X, Y)
# =========== Part 1: Regularized Logistic Regression ============
# 向高维扩展
def mapFeature(x1, x2):
degree = 6
col = int(degree*(degree+1)/2+degree+1)
out = np.ones((np.size(x1, 0), col))
count = 1
for i in range(1, degree+1):
for j in range(i+1):
out[:, count] = np.power(x1, i-j)*np.power(x2, j)
count += 1
return out
X = mapFeature(X[:, 0], X[:, 1])
init_theta = np.zeros((np.size(X, 1),))
lamd = 1
# sigmoid函数
def sigmoid(z):
g = 1/(1+np.exp(-1*z))
return g
# 损失函数
def costFuncReg(theta, x, y, lam):
m = np.size(y, 0)
h = sigmoid(x.dot(theta))
j=-1/m*(y.dot(np.log(h))+(1-y).dot(np.log(1-h)))+lam/(2*m)*theta[1:].dot(theta[1:])
return j
# 梯度函数
def gradFuncReg(theta, x, y, lam):
m = np.size(y, 0)
h = sigmoid(x.dot(theta))
grad = np.zeros(np.size(theta, 0))
grad[0] = 1/m*(x[:, 0].dot(h-y))
grad[1:] = 1/m*(x[:, 1:].T.dot(h-y))+lam*theta[1:]/m
return grad
cost = costFuncReg(init_theta, X, Y, lamd)
print('Cost at initial theta (zeros): ', cost)
_ = input('Press [Enter] to continue.')
# ============= Part 2: Regularization and Accuracies =============
init_theta = np.zeros((np.size(X, 1),))
lamd = 1
result = op.minimize(costFuncReg, x0=init_theta, method='BFGS', jac=gradFuncReg, args=(X, Y, lamd))
theta = result.x
def plotDecisionBoundary(theta, x, y):
pos = np.where(y == 1)
neg = np.where(y == 0)
p1 = plt.scatter(x[pos, 1], x[pos, 2], marker='+', s=60, color='r')
p2 = plt.scatter(x[neg, 1], x[neg, 2], marker='o', s=60, color='y')
u = np.linspace(-1, 1.5, 50)
v = np.linspace(-1, 1.5, 50)
z = np.zeros((np.size(u, 0), np.size(v, 0)))
for i in range(np.size(u, 0)):
for j in range(np.size(v, 0)):
z[i, j] = mapFeature(np.array([u[i]]), np.array([v[j]])).dot(theta)
z = z.T
[um, vm] = np.meshgrid(u, v)
plt.contour(um, vm, z, levels=[0], lw=2)
plt.legend((p1, p2), ('Admitted', 'Not admitted'), loc='upper right', fontsize=8)
plt.xlabel('Microchip Test 1')
plt.ylabel('Microchip Test 2')
plt.title('lambda = 1')
plt.show()
plotDecisionBoundary(theta, X, Y)
# 预测给定值
def predict(theta, x):
m = np.size(X, 0)
p = np.zeros((m,))
pos = np.where(x.dot(theta) >= 0)
neg = np.where(x.dot(theta) < 0)
p[pos] = 1
p[neg] = 0
return p
p = predict(theta, X)
print('Train Accuracy: ', np.sum(p == Y)/np.size(Y, 0))

View File

@@ -0,0 +1,100 @@
34.62365962451697,78.0246928153624,0
30.28671076822607,43.89499752400101,0
35.84740876993872,72.90219802708364,0
60.18259938620976,86.30855209546826,1
79.0327360507101,75.3443764369103,1
45.08327747668339,56.3163717815305,0
61.10666453684766,96.51142588489624,1
75.02474556738889,46.55401354116538,1
76.09878670226257,87.42056971926803,1
84.43281996120035,43.53339331072109,1
95.86155507093572,38.22527805795094,0
75.01365838958247,30.60326323428011,0
82.30705337399482,76.48196330235604,1
69.36458875970939,97.71869196188608,1
39.53833914367223,76.03681085115882,0
53.9710521485623,89.20735013750205,1
69.07014406283025,52.74046973016765,1
67.94685547711617,46.67857410673128,0
70.66150955499435,92.92713789364831,1
76.97878372747498,47.57596364975532,1
67.37202754570876,42.83843832029179,0
89.67677575072079,65.79936592745237,1
50.534788289883,48.85581152764205,0
34.21206097786789,44.20952859866288,0
77.9240914545704,68.9723599933059,1
62.27101367004632,69.95445795447587,1
80.1901807509566,44.82162893218353,1
93.114388797442,38.80067033713209,0
61.83020602312595,50.25610789244621,0
38.78580379679423,64.99568095539578,0
61.379289447425,72.80788731317097,1
85.40451939411645,57.05198397627122,1
52.10797973193984,63.12762376881715,0
52.04540476831827,69.43286012045222,1
40.23689373545111,71.16774802184875,0
54.63510555424817,52.21388588061123,0
33.91550010906887,98.86943574220611,0
64.17698887494485,80.90806058670817,1
74.78925295941542,41.57341522824434,0
34.1836400264419,75.2377203360134,0
83.90239366249155,56.30804621605327,1
51.54772026906181,46.85629026349976,0
94.44336776917852,65.56892160559052,1
82.36875375713919,40.61825515970618,0
51.04775177128865,45.82270145776001,0
62.22267576120188,52.06099194836679,0
77.19303492601364,70.45820000180959,1
97.77159928000232,86.7278223300282,1
62.07306379667647,96.76882412413983,1
91.56497449807442,88.69629254546599,1
79.94481794066932,74.16311935043758,1
99.2725269292572,60.99903099844988,1
90.54671411399852,43.39060180650027,1
34.52451385320009,60.39634245837173,0
50.2864961189907,49.80453881323059,0
49.58667721632031,59.80895099453265,0
97.64563396007767,68.86157272420604,1
32.57720016809309,95.59854761387875,0
74.24869136721598,69.82457122657193,1
71.79646205863379,78.45356224515052,1
75.3956114656803,85.75993667331619,1
35.28611281526193,47.02051394723416,0
56.25381749711624,39.26147251058019,0
30.05882244669796,49.59297386723685,0
44.66826172480893,66.45008614558913,0
66.56089447242954,41.09209807936973,0
40.45755098375164,97.53518548909936,1
49.07256321908844,51.88321182073966,0
80.27957401466998,92.11606081344084,1
66.74671856944039,60.99139402740988,1
32.72283304060323,43.30717306430063,0
64.0393204150601,78.03168802018232,1
72.34649422579923,96.22759296761404,1
60.45788573918959,73.09499809758037,1
58.84095621726802,75.85844831279042,1
99.82785779692128,72.36925193383885,1
47.26426910848174,88.47586499559782,1
50.45815980285988,75.80985952982456,1
60.45555629271532,42.50840943572217,0
82.22666157785568,42.71987853716458,0
88.9138964166533,69.80378889835472,1
94.83450672430196,45.69430680250754,1
67.31925746917527,66.58935317747915,1
57.23870631569862,59.51428198012956,1
80.36675600171273,90.96014789746954,1
68.46852178591112,85.59430710452014,1
42.0754545384731,78.84478600148043,0
75.47770200533905,90.42453899753964,1
78.63542434898018,96.64742716885644,1
52.34800398794107,60.76950525602592,0
94.09433112516793,77.15910509073893,1
90.44855097096364,87.50879176484702,1
55.48216114069585,35.57070347228866,0
74.49269241843041,84.84513684930135,1
89.84580670720979,45.35828361091658,1
83.48916274498238,48.38028579728175,1
42.2617008099817,87.10385094025457,1
99.31500880510394,68.77540947206617,1
55.34001756003703,64.9319380069486,1
74.77589300092767,89.52981289513276,1

View File

@@ -0,0 +1,118 @@
0.051267,0.69956,1
-0.092742,0.68494,1
-0.21371,0.69225,1
-0.375,0.50219,1
-0.51325,0.46564,1
-0.52477,0.2098,1
-0.39804,0.034357,1
-0.30588,-0.19225,1
0.016705,-0.40424,1
0.13191,-0.51389,1
0.38537,-0.56506,1
0.52938,-0.5212,1
0.63882,-0.24342,1
0.73675,-0.18494,1
0.54666,0.48757,1
0.322,0.5826,1
0.16647,0.53874,1
-0.046659,0.81652,1
-0.17339,0.69956,1
-0.47869,0.63377,1
-0.60541,0.59722,1
-0.62846,0.33406,1
-0.59389,0.005117,1
-0.42108,-0.27266,1
-0.11578,-0.39693,1
0.20104,-0.60161,1
0.46601,-0.53582,1
0.67339,-0.53582,1
-0.13882,0.54605,1
-0.29435,0.77997,1
-0.26555,0.96272,1
-0.16187,0.8019,1
-0.17339,0.64839,1
-0.28283,0.47295,1
-0.36348,0.31213,1
-0.30012,0.027047,1
-0.23675,-0.21418,1
-0.06394,-0.18494,1
0.062788,-0.16301,1
0.22984,-0.41155,1
0.2932,-0.2288,1
0.48329,-0.18494,1
0.64459,-0.14108,1
0.46025,0.012427,1
0.6273,0.15863,1
0.57546,0.26827,1
0.72523,0.44371,1
0.22408,0.52412,1
0.44297,0.67032,1
0.322,0.69225,1
0.13767,0.57529,1
-0.0063364,0.39985,1
-0.092742,0.55336,1
-0.20795,0.35599,1
-0.20795,0.17325,1
-0.43836,0.21711,1
-0.21947,-0.016813,1
-0.13882,-0.27266,1
0.18376,0.93348,0
0.22408,0.77997,0
0.29896,0.61915,0
0.50634,0.75804,0
0.61578,0.7288,0
0.60426,0.59722,0
0.76555,0.50219,0
0.92684,0.3633,0
0.82316,0.27558,0
0.96141,0.085526,0
0.93836,0.012427,0
0.86348,-0.082602,0
0.89804,-0.20687,0
0.85196,-0.36769,0
0.82892,-0.5212,0
0.79435,-0.55775,0
0.59274,-0.7405,0
0.51786,-0.5943,0
0.46601,-0.41886,0
0.35081,-0.57968,0
0.28744,-0.76974,0
0.085829,-0.75512,0
0.14919,-0.57968,0
-0.13306,-0.4481,0
-0.40956,-0.41155,0
-0.39228,-0.25804,0
-0.74366,-0.25804,0
-0.69758,0.041667,0
-0.75518,0.2902,0
-0.69758,0.68494,0
-0.4038,0.70687,0
-0.38076,0.91886,0
-0.50749,0.90424,0
-0.54781,0.70687,0
0.10311,0.77997,0
0.057028,0.91886,0
-0.10426,0.99196,0
-0.081221,1.1089,0
0.28744,1.087,0
0.39689,0.82383,0
0.63882,0.88962,0
0.82316,0.66301,0
0.67339,0.64108,0
1.0709,0.10015,0
-0.046659,-0.57968,0
-0.23675,-0.63816,0
-0.15035,-0.36769,0
-0.49021,-0.3019,0
-0.46717,-0.13377,0
-0.28859,-0.060673,0
-0.61118,-0.067982,0
-0.66302,-0.21418,0
-0.59965,-0.41886,0
-0.72638,-0.082602,0
-0.83007,0.31213,0
-0.72062,0.53874,0
-0.59389,0.49488,0
-0.48445,0.99927,0
-0.0063364,0.99927,0
0.63265,-0.030612,0

View File

@@ -0,0 +1,103 @@
import numpy as np
import matplotlib.pylab as plt
import scipy.io as sio
import math
import scipy.optimize as op
input_layer_size = 400
num_labels = 10
# =========== Part 1: Loading and Visualizing Data =============
print('Loading and Visualizing Data ...')
matinfo = sio.loadmat('ex3data1.mat')
X = matinfo['X']
Y = matinfo['y'][:, 0]
m = np.size(X, 0)
rand_indices = np.random.permutation(m)
sel = X[rand_indices[0:100], :]
# 显示随机100个图像, 疑问:最后的数组需要转置才会显示正的图像
def displayData(x):
width = round(math.sqrt(np.size(x, 1)))
m, n = np.shape(x)
height = int(n/width)
# 显示图像的数量
drows = math.floor(math.sqrt(m))
dcols = math.ceil(m/drows)
pad = 1
# 建立一个空白“背景布”
darray = -1*np.ones((pad+drows*(height+pad), pad+dcols*(width+pad)))
curr_ex = 0
for j in range(drows):
for i in range(dcols):
if curr_ex >= m:
break
max_val = np.max(np.abs(X[curr_ex, :]))
darray[pad+j*(height+pad):pad+j*(height+pad)+height, pad+i*(width+pad):pad+i*(width+pad)+width]\
= x[curr_ex, :].reshape((height, width))/max_val
curr_ex += 1
if curr_ex >= m:
break
plt.imshow(darray.T, cmap='gray')
plt.show()
displayData(sel)
_ = input('Press [Enter] to continue.')
# ============ Part 2: Vectorize Logistic Regression ============
print('Training One-vs-All Logistic Regression...')
# sigmoid函数
def sigmoid(z):
g = 1/(1+np.exp(-1*z))
return g
# 损失函数
def lrCostFunc(theta, x, y, lam):
m = np.size(y, 0)
h = sigmoid(x.dot(theta))
j = -1/m*(y.dot(np.log(h))+(1-y).dot(np.log(1-h)))+lamb*(theta[1:].dot(theta[1:]))/(2*m)
return j
# 梯度函数
def lrGradFunc(theta, x, y, lam):
m = np.size(y, 0)
h = sigmoid(x.dot(theta))
grad = np.zeros(np.size(theta))
grad[0] = 1 / m * (x[:, 0].dot(h-y))
grad[1:] = 1/m*(x[:, 1:].T.dot(h-y))+lam/m*theta[1:]
return grad
# 获取多个分类器的theta值
def oneVsAll(x, y, num_labels, lam):
m, n = np.shape(x)
all_theta = np.zeros((num_labels, n+1))
x = np.concatenate((np.ones((m, 1)), x), axis=1)
for i in range(num_labels):
num = 10 if i == 0 else i
init_theta = np.zeros((n+1,))
result = op.minimize(lrCostFunc, init_theta, method='BFGS'\
,jac=lrGradFunc, args=(x, 1*(y == num), lam), options={'maxiter': 50})
all_theta[i, :] = result.x
return all_theta
lamb = 0.1
all_theta = oneVsAll(X, Y, num_labels, lamb)
_ = input('Press [Enter] to continue.')
# ================ Part 3: Predict for One-Vs-All ================
# 预测值函数
def predictOneVsAll(all_theta, x):
m = np.size(x, 0)
x = np.concatenate((np.ones((m, 1)), x), axis=1)
p = np.argmax(x.dot(all_theta.T), axis=1)
return p
pred = predictOneVsAll(all_theta, X)
print('Training Set Accuracy: ', np.sum(pred == (Y % 10))/np.size(Y, 0))

View File

@@ -0,0 +1,91 @@
import numpy as np
import matplotlib.pylab as plt
import scipy.io as sio
import math
# 显示随机100个图像, 疑问:最后的数组需要转置才会显示正的图像
def displayData(x):
width = round(math.sqrt(np.size(x, 1)))
m, n = np.shape(x)
height = int(n/width)
# 显示图像的数量
drows = math.floor(math.sqrt(m))
dcols = math.ceil(m/drows)
pad = 1
# 建立一个空白“背景布”
darray = -1*np.ones((pad+drows*(height+pad), pad+dcols*(width+pad)))
curr_ex = 0
for j in range(drows):
for i in range(dcols):
if curr_ex >= m:
break
max_val = np.max(np.abs(X[curr_ex, :]))
darray[pad+j*(height+pad):pad+j*(height+pad)+height, pad+i*(width+pad):pad+i*(width+pad)+width]\
= x[curr_ex, :].reshape((height, width))/max_val
curr_ex += 1
if curr_ex >= m:
break
plt.imshow(darray.T, cmap='gray')
plt.show()
input_layer_size = 400
hidden_layer_size = 25
num_labels = 10
# =========== Part 1: Loading and Visualizing Data =============
# LoadData
print('Loading and Visualizing Data ...')
datainfo = sio.loadmat('ex3data1.mat')
X = datainfo['X']
Y = datainfo['y'][:, 0]
m = np.size(X, 0)
rand_indices = np.random.permutation(m)
sel = X[rand_indices[0:100], :]
displayData(sel)
_ = input('Press [Enter] to continue.')
# ================ Part 2: Loading Pameters ================
print('Loading Saved Neural Network Parameters ...')
weightinfo = sio.loadmat('ex3weights.mat')
theta1 = weightinfo['Theta1']
theta2 = weightinfo['Theta2']
# ================= Part 3: Implement Predict =================
# sigmoid函数
def sigmoid(z):
g = 1/(1+np.exp(-1*z))
return g
# 预测函数
def predict(t1, t2, x):
m = np.size(x, 0)
x = np.concatenate((np.ones((m, 1)), x), axis=1)
temp1 = sigmoid(x.dot(theta1.T))
temp = np.concatenate((np.ones((m, 1)), temp1), axis=1)
temp2 = sigmoid(temp.dot(theta2.T))
p = np.argmax(temp2, axis=1)+1
return p
pred = predict(theta1, theta2, X)
print('Training Set Accuracy: ', np.sum(pred == Y)/np.size(Y, 0))
_ = input('Press [Enter] to continue.')
# 随机展示图像
num = 10
rindex = np.random.permutation(m)
for i in range(num):
print('Displaying Example Image')
displayData(X[rindex[i]:rindex[i]+1, :])
pred = predict(theta1, theta2, X[rindex[i]:rindex[i]+1, :])
print('Neural Network Prediction: %d (digit %d)' % (pred, pred % 10))
_ = input('Press [Enter] to continue.')

View File

@@ -0,0 +1,277 @@
import numpy as np
import matplotlib.pylab as plt
import scipy.io as sio
import math
import scipy.linalg as slin
import scipy.optimize as op
input_layer_size = 400
hidden_layer_size = 25
num_labels = 10
# =========== Part 1: Loading and Visualizing Data =============
# 显示随机100个图像, 疑问:最后的数组需要转置才会显示正的图像
def displayData(x):
width = round(math.sqrt(np.size(x, 1)))
m, n = np.shape(x)
height = int(n/width)
# 显示图像的数量
drows = math.floor(math.sqrt(m))
dcols = math.ceil(m/drows)
pad = 1
# 建立一个空白“背景布”
darray = -1*np.ones((pad+drows*(height+pad), pad+dcols*(width+pad)))
curr_ex = 0
for j in range(drows):
for i in range(dcols):
if curr_ex >= m:
break
max_val = np.max(np.abs(X[curr_ex, :]))
darray[pad+j*(height+pad):pad+j*(height+pad)+height, pad+i*(width+pad):pad+i*(width+pad)+width]\
= x[curr_ex, :].reshape((height, width))/max_val
curr_ex += 1
if curr_ex >= m:
break
plt.imshow(darray.T, cmap='gray')
plt.show()
print('Loading and Visualizing Data ...')
datainfo = sio.loadmat('ex4data1.mat')
X = datainfo['X']
Y = datainfo['y'][:, 0]
m = np.size(X, 0)
rand_indices = np.random.permutation(m)
sel = X[rand_indices[0:100], :]
displayData(sel)
_ = input('Press [Enter] to continue.')
# ================ Part 2: Loading Parameters ================
print('Loading Saved Neural Network Parameters ...')
thetainfo = sio.loadmat('ex4weights.mat')
theta1 = thetainfo['Theta1']
theta2 = thetainfo['Theta2']
nn_params = np.concatenate((theta1.flatten(), theta2.flatten()))
# ================ Part 3: Compute Cost (Feedforward) ================
# sigmoid函数
def sigmoid(z):
g = 1/(1+np.exp(-1*z))
return g
# sigmoid函数导数
def sigmoidGradient(z):
g = sigmoid(z)*(1-sigmoid(z))
return g
# 损失函数计算
def nnCostFunc(params, input_layer_size, hidden_layer_size, num_labels, x, y, lamb):
theta1 = params[0:hidden_layer_size*(input_layer_size+1)].reshape((hidden_layer_size, input_layer_size+1))
theta2 = params[(hidden_layer_size*(input_layer_size+1)):].reshape((num_labels, hidden_layer_size+1))
m = np.size(x, 0)
# 前向传播 --- 下标:0代表1, 9代表10
a1 = np.concatenate((np.ones((m, 1)), x), axis=1)
z2 = a1.dot(theta1.T); l2 = np.size(z2, 0)
a2 = np.concatenate((np.ones((l2, 1)), sigmoid(z2)), axis=1)
z3 = a2.dot(theta2.T)
a3 = sigmoid(z3)
yt = np.zeros((m, num_labels))
yt[np.arange(m), y-1] = 1
j = np.sum(-yt*np.log(a3)-(1-yt)*np.log(1-a3))
# 向后传播
delta3 = a3-yt
delta2 = delta3.dot(theta2)*sigmoidGradient(np.concatenate((np.ones((l2, 1)), z2), axis=1))
theta2_grad = delta3.T.dot(a2)
theta1_grad = delta2[:, 1:].T.dot(a1)
j = j/m
theta2_grad = theta2_grad/m
theta2_grad[:, 1:] = theta2_grad[:, 1:]+lamb/m*theta2[:, 1:]
theta1_grad = theta1_grad/m
theta1_grad[:, 1:] = theta1_grad[:, 1:]+lamb/m*theta1[:, 1:]
reg_cost = np.sum(np.power(theta1[:, 1:], 2)) + np.sum(np.power(theta2[:, 1:], 2))
j = j+1/(2*m)*lamb*reg_cost
grad = np.concatenate((theta1_grad.flatten(), theta2_grad.flatten()))
return j, grad
print('Feedforward Using Neural Network ...')
lamb = 0
j, _ = nnCostFunc(nn_params, input_layer_size, hidden_layer_size, num_labels, X, Y, lamb)
print('Cost at parameters (loaded from ex4weights): %f \n(this value should be about 0.287629)' % j)
_ = input('Press [Enter] to continue.')
# =============== Part 4: Implement Regularization ===============
print('Checking Cost Function (w/ Regularization) ...')
lamb = 1
j, _ = nnCostFunc(nn_params, input_layer_size, hidden_layer_size, num_labels, X, Y, lamb)
print('Cost at parameters (loaded from ex4weights): %f \n(this value should be about 0.383770)' % j)
_ = input('Press [Enter] to continue.')
# ================ Part 5: Sigmoid Gradient ================
print('Evaluating sigmoid gradient...')
g = sigmoidGradient(np.array([1, -0.5, 0, 0.5, 1]))
print(g)
_ = input('Press [Enter] to continue.')
# ================ Part 6: Initializing Pameters ================
# 随机确定初始theta参数
def randInitializeWeight(lin, lout):
epsilon_init = 0.12
w = np.random.rand(lout, lin+1)*2*epsilon_init-epsilon_init;
return w
print('Initializing Neural Network Parameters ...')
init_theta1 = randInitializeWeight(input_layer_size, hidden_layer_size)
init_theta2 = randInitializeWeight(hidden_layer_size, num_labels)
init_nn_params = np.concatenate((init_theta1.flatten(), init_theta2.flatten()))
# =============== Part 7: Implement Backpropagation ===============
# 调试时的参数初始化
def debugInitWeights(fout, fin):
w = np.sin(np.arange(fout*(fin+1))+1).reshape(fout, fin+1)/10
return w
# 数值法计算梯度
def computeNumericalGradient(J, theta, args):
numgrad = np.zeros(np.size(theta))
perturb = np.zeros(np.size(theta))
epsilon = 1e-4
for i in range(np.size(theta)):
perturb[i] = epsilon
loss1, _ = J(theta-perturb, *args)
loss2, _ = J(theta+perturb, *args)
numgrad[i] = (loss2-loss1)/(2*epsilon)
perturb[i] = 0
return numgrad
# 检查神经网络的梯度
def checkNNGradients(lamb):
input_layer_size = 3
hidden_layer_size = 5
num_labels = 3
m = 5
theta1 = debugInitWeights(hidden_layer_size, input_layer_size)
theta2 = debugInitWeights(num_labels, hidden_layer_size)
x = debugInitWeights(m, input_layer_size-1)
y = 1+(np.arange(m)+1) % num_labels
nn_params = np.concatenate((theta1.flatten(), theta2.flatten()))
cost, grad = nnCostFunc(nn_params, input_layer_size, hidden_layer_size, num_labels, x, y, lamb)
numgrad = computeNumericalGradient(nnCostFunc, nn_params,\
(input_layer_size, hidden_layer_size, num_labels, x, y, lamb))
print(numgrad, '\n', grad)
print('The above two columns you get should be very similar.\n \
(Left-Your Numerical Gradient, Right-Analytical Gradient)')
diff = slin.norm(numgrad-grad)/slin.norm(numgrad+grad)
print('If your backpropagation implementation is correct, then \n\
the relative difference will be small (less than 1e-9). \n\
\nRelative Difference: ', diff)
print('Checking Backpropagation...')
checkNNGradients(0)
_ = input('Press [Enter] to continue.')
# =============== Part 8: Implement Regularization ===============
print('Checking Backpropagation (w/ Regularization) ...')
lamb = 3
checkNNGradients(lamb)
debug_j, _ =nnCostFunc(nn_params, input_layer_size, hidden_layer_size, num_labels, X, Y, lamb)
print('Cost at (fixed) debugging parameters (w/ lambda = 10): %f \n(this value should be about 0.576051)' % debug_j)
_ = input('Press [Enter] to continue.')
# =================== Part 8: Training NN ===================
# 损失函数
def nnCost(params, input_layer_size, hidden_layer_size, num_labels, x, y, lamb):
theta1 = params[0:hidden_layer_size * (input_layer_size + 1)].reshape(hidden_layer_size, input_layer_size + 1)
theta2 = params[hidden_layer_size * (input_layer_size + 1):].reshape(num_labels, hidden_layer_size + 1)
m = np.size(x, 0)
# 前向传播 --- 下标:0代表1, 9代表10
a1 = np.concatenate((np.ones((m, 1)), x), axis=1)
z2 = a1.dot(theta1.T);
l2 = np.size(z2, 0)
a2 = np.concatenate((np.ones((l2, 1)), sigmoid(z2)), axis=1)
z3 = a2.dot(theta2.T)
a3 = sigmoid(z3)
yt = np.zeros((m, num_labels))
yt[np.arange(m), y - 1] = 1
j = np.sum(-yt * np.log(a3) - (1 - yt) * np.log(1 - a3))
# 向后传播
j = j / m
reg_cost = np.sum(np.power(theta1[:, 1:], 2)) + np.sum(np.power(theta2[:, 1:], 2))
j = j + 1 / (2 * m) * lamb * reg_cost
return j
# 梯度函数
def nnGrad(params, input_layer_size, hidden_layer_size, num_labels, x, y, lamb):
theta1 = params[0:hidden_layer_size * (input_layer_size + 1)].reshape(hidden_layer_size, input_layer_size + 1)
theta2 = params[(hidden_layer_size * (input_layer_size + 1)):].reshape(num_labels, hidden_layer_size + 1)
m = np.size(x, 0)
# 前向传播 --- 下标:0代表1, 9代表10
a1 = np.concatenate((np.ones((m, 1)), x), axis=1)
z2 = a1.dot(theta1.T);
l2 = np.size(z2, 0)
a2 = np.concatenate((np.ones((l2, 1)), sigmoid(z2)), axis=1)
z3 = a2.dot(theta2.T)
a3 = sigmoid(z3)
yt = np.zeros((m, num_labels))
yt[np.arange(m), y - 1] = 1
# 向后传播
delta3 = a3 - yt
delta2 = delta3.dot(theta2) * sigmoidGradient(np.concatenate((np.ones((l2, 1)), z2), axis=1))
theta2_grad = delta3.T.dot(a2)
theta1_grad = delta2[:, 1:].T.dot(a1)
theta2_grad = theta2_grad / m
theta2_grad[:, 1:] = theta2_grad[:, 1:] + lamb / m * theta2[:, 1:]
theta1_grad = theta1_grad / m
theta1_grad[:, 1:] = theta1_grad[:, 1:] + lamb / m * theta1[:, 1:]
grad = np.concatenate((theta1_grad.flatten(), theta2_grad.flatten()))
return grad
print('Training Neural Network...')
lamb = 1
param = op.fmin_cg(nnCost, init_nn_params, fprime=nnGrad, \
args=(input_layer_size, hidden_layer_size, num_labels, X, Y, lamb), maxiter=50)
theta1 = param[0: hidden_layer_size*(input_layer_size+1)].reshape(hidden_layer_size, input_layer_size+1)
theta2 = param[hidden_layer_size*(input_layer_size+1):].reshape(num_labels, hidden_layer_size+1)
_ = input('Press [Enter] to continue.')
# ================= Part 9: Visualize Weights =================
print('Visualizing Neural Network...')
displayData(theta1[:, 1:])
_ = input('Press [Enter] to continue.')
# ================= Part 10: Implement Predict =================
# 预测函数
def predict(t1, t2, x):
m = np.size(x, 0)
x = np.concatenate((np.ones((m, 1)), x), axis=1)
temp1 = sigmoid(x.dot(theta1.T))
temp = np.concatenate((np.ones((m, 1)), temp1), axis=1)
temp2 = sigmoid(temp.dot(theta2.T))
p = np.argmax(temp2, axis=1)+1
return p
pred = predict(theta1, theta2, X)
print('Training Set Accuracy: ', np.sum(pred == Y)/np.size(Y, 0))

View File

@@ -0,0 +1,9 @@
def func(x,y,z):
return x+y+z
def func2(f, para1, args):
back=f(para1, *args)
print(back)
func2(func,1,(4,2))

View File

@@ -0,0 +1,198 @@
import numpy as np
import scipy.io as sio
import matplotlib.pylab as plt
import scipy.optimize as op
from numpy import linalg as la
# =========== Part 1: Loading and Visualizing Data =============
print('Loading and Visualizing Data ...')
datainfo = sio.loadmat('ex5data1.mat')
X = datainfo['X'][:, 0]
Y = datainfo['y'][:, 0]
Xtest = datainfo['Xtest'][:, 0]
Ytest = datainfo['ytest'][:, 0]
Xval = datainfo['Xval'][:, 0]
Yval = datainfo['yval'][:, 0]
m = np.size(X, 0)
plt.plot(X, Y, 'rx', ms=10, mew=1.5)
plt.xlabel('Change in water level (x)')
plt.ylabel('Water flowing out of the dam (y)')
plt.show()
_ = input('Press [Enter] to continue.')
# =========== Part 2 & 3: Regularized Linear Regression Cost and Gradient=============
# 线性回归损失函数
def linRegCostFunc(theta, x, y, lamb):
m = np.size(y, 0)
j = 1/(2*m)*(x.dot(theta)-y).T.dot(x.dot(theta)-y)+lamb/(2*m)*(theta[1:].dot(theta[1:]))
return j
# 线性回归梯度函数
def linRegGradFunc(theta, x, y, lamb):
m = np.size(y, 0)
grad = np.zeros(np.shape(theta))
grad[0] = 1/m*(x.dot(theta)-y).dot(x[:, 0])
grad[1:] = 1/m*(x[:, 1:]).T.dot(x.dot(theta)-y)+lamb/m*theta[1:]
return grad
theta = np.array([1.0, 1.0])
j = linRegCostFunc(theta, np.vstack((np.ones((m,)), X)).T, Y, 1)
grad = linRegGradFunc(theta, np.vstack((np.ones((m,)), X)).T, Y, 1)
print('Cost at theta = [1 ; 1]: %f \
\n(this value should be about 303.993192)' % j)
print('Gradient at theta = [1 ; 1]: [%f; %f] \
\n(this value should be about [-15.303016; 598.250744])' % (grad[0], grad[1]))
# =========== Part 4: Train Linear Regression =============
# 训练线性回归
def trainLinReg(x, y, lamb):
init_theta = np.zeros((np.size(x, 1),))
theta = op.fmin_cg(linRegCostFunc, init_theta, fprime=linRegGradFunc, maxiter=200, args=(x, y, lamb))
return theta
lamb = 0
theta = trainLinReg(np.vstack((np.ones((m,)), X)).T, Y, lamb)
# 绘制图像
plt.plot(X, Y, 'rx', ms=10, mew=1.5)
plt.plot(X, np.vstack((np.ones((m,)), X)).T.dot(theta), '--', lw=2)
plt.xlabel('Change in water level (x)')
plt.ylabel('Water flowing out of the dam (y)')
plt.show()
_ = input('Press [Enter] to continue.')
# =========== Part 5: Learning Curve for Linear Regression =============
# 学习曲线
def learningCurve(x, y, xval, yval, lamb):
m = np.size(x, 0)
err_train = np.zeros((m,))
err_val = np.zeros((m,))
for i in range(m):
theta = trainLinReg(x[0:i+1, :], y[0:i+1], lamb)
err_train[i] = linRegCostFunc(theta, x[0:i+1, :], y[0:i+1], 0)
err_val[i] = linRegCostFunc(theta, xval, yval, 0)
return err_train, err_val
mval = np.size(Xval, 0)
err_train, err_val = learningCurve(np.vstack((np.ones((m,)), X)).T, Y \
,np.vstack((np.ones((mval,)), Xval)).T, Yval, lamb)
# 绘制图像
plt.plot(np.arange(m)+1, err_train, 'b-', label='Train')
plt.plot(np.arange(m)+1, err_val, 'r-', label='Cross Validation')
plt.axis([0, 13, 0, 150])
plt.legend(loc='upper right')
plt.title('Learning curve for linear regression')
plt.xlabel('Number of training examples')
plt.ylabel('Error')
plt.show()
print('Training Examples Train Error Cross Validation Error')
for i in range(m):
print('\t%d\t\t\t\t%f\t\t\t%f' % (i+1, err_train[i], err_val[i]))
_ = input('Press [Enter] to continue.')
# =========== Part 6: Feature Mapping for Polynomial Regression =============
# 多项式映射
def polyFeature(x, p):
m = np.size(x, 0)
x_poly = np.zeros((m, p))
for i in range(p):
x_poly[:, i] = np.power(x, i+1)
return x_poly
# 归一化处理
def featureNormalize(x):
mu = np.mean(x, 0)
sigma = np.std(x, 0, ddof=1)
x_norm = (x-mu)/sigma
return x_norm, mu, sigma
p = 8
X_p = polyFeature(X, p)
X_p, mu, sigma = featureNormalize(X_p)
X_poly = np.concatenate((np.ones((m, 1)), X_p), axis=1)
ltest= np.size(Xtest, 0)
X_p_test = polyFeature(Xtest, p)
X_p_test = (X_p_test-mu)/sigma
X_poly_test = np.concatenate((np.ones((ltest, 1)), X_p_test), axis=1)
lval = np.size(Xval, 0)
X_v_test = polyFeature(Xval, p)
X_v_test = (X_v_test-mu)/sigma
X_poly_val = np.concatenate((np.ones((lval, 1)), X_v_test), axis=1)
print('Normalized Training Example 1: \n', X_poly[0, :])
_ = input('Press [Enter] to continue.')
# =========== Part 7: Learning Curve for Polynomial Regression =============
# 曲线拟合
def plotFit(min_x, max_x, mu, sigma, p):
x = np.arange(min_x-15, max_x+25, 0.05)
x_p = polyFeature(x, p)
x_p = (x_p-mu)/sigma
l = np.size(x_p, 0)
x_poly = np.concatenate((np.ones((l, 1)), x_p), axis=1)
return x, x_poly.dot(theta)
lamb = 0
theta = trainLinReg(X_poly, Y, lamb)
x_simu, y_simu = plotFit(np.min(X), np.max(X), mu, sigma, p)
fig1 = plt.figure(1)
ax = fig1.add_subplot(111)
ax.plot(X, Y, 'rx', ms=10, mew=1.5)
ax.plot(x_simu, y_simu, '--', lw=2)
ax.set_xlabel('Change in water level (x)')
ax.set_ylabel('Water flowing out of the dam (y)')
fig1.suptitle('Polynomial Regression Fit (lambda = 0)')
err_train, err_val = learningCurve(X_poly, Y, X_poly_val, Yval, lamb)
fig2 = plt.figure(2)
ax2 = fig2.add_subplot(111)
ax2.plot(np.arange(m)+1, err_train, 'b', label='Train')
ax2.plot(np.arange(m)+1, err_val, 'r', label='Cross Validation')
ax2.set_xlabel('Number of training examples')
ax2.set_ylabel('Error')
handles2, labels2 = ax2.get_legend_handles_labels()
ax2.legend(handles2, labels2)
ax2.set_xlim([0, 13])
ax2.set_ylim([0, 100])
fig2.suptitle('PPolynomial Regression Learning Curve (lambda = 0)')
plt.show()
print('Polynomial Regression (lambda = 0)')
print('Training Examples\tTrain Error\tCross Validation Error')
for i in range(m):
print(' \t%d\t\t%f\t%f' % (i+1, err_train[i], err_val[i]))
_ = input('Press [Enter] to continue.')
# =========== Part 8: Validation for Selecting Lambda =============
def validationCurve(x, y, xval, yval):
lamb_vec = [0, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1, 3, 10]
err_train = np.zeros((len(lamb_vec,)))
err_val = np.zeros((len(lamb_vec,)))
for i in range(len(lamb_vec)):
lamb = lamb_vec[i]
theta = trainLinReg(x, y, lamb)
err_train[i] = linRegCostFunc(theta, x, y, 0)
err_val[i] = linRegCostFunc(theta, xval, yval, 0)
return lamb_vec, err_train, err_val
lambda_vec, err_train, err_val = validationCurve(X_poly, Y, X_poly_val, Yval)
plt.plot(lambda_vec, err_train, 'b', label='Train')
plt.plot(lambda_vec, err_val, 'r', label='Cross Validation')
plt.xlabel('lambda')
plt.ylabel('Error')
plt.legend(loc='upper right')
plt.show()
print('lambda\t\tTrain Error\tValidation Error')
for i in range(len(lambda_vec)):
print(' %f\t%f\t%f' % (lambda_vec[i], err_train[i], err_val[i]))
_ = input('Press [Enter] to continue.')

View File

@@ -0,0 +1,121 @@
import numpy as np
import matplotlib.pyplot as plt
import scipy.io as sio
from sklearn import svm
# =============== Part 1: Loading and Visualizing Data ================
# 数据可视化
def plotData(x, y):
pos = np.where(y == 1)
neg = np.where(y == 0)
plt.plot(x[pos, 0], x[pos, 1], 'k+', lw=1, ms=7)
plt.plot(x[neg, 0], x[neg, 1], 'ko', mfc='y', ms=7)
print('Loading and Visualizing Data ...')
datainfo = sio.loadmat('ex6data1.mat')
X = datainfo['X']
Y = datainfo['y'][:, 0]
plotData(X, Y)
plt.show()
_ = input('Press [Enter] to continue.')
# ==================== Part 2: Training Linear SVM ====================
# 线性可视化
def visualBoundaryLinear(x, y, theta, b):
xp = np.linspace(np.min(x[:, 0]), np.max(x[:, 0]), 100)
yp = -(theta[0]*xp+b)/theta[1]
plotData(x, y)
plt.plot(xp, yp, '-b')
print('Training Linear SVM ...')
c = 1.0
clf = svm.SVC(C=c, kernel='linear')
clf.fit(X, Y)
theta = clf.coef_.flatten()
b = clf.intercept_
visualBoundaryLinear(X, Y, theta, b)
plt.show()
_ = input('Press [Enter] to continue.')
# =============== Part 3: Implementing Gaussian Kernel ===============
# 高斯核
def gaussianKernel(x1, x2, sigma):
sim = np.exp(-(x1-x2).dot(x1-x2)/(2*sigma**2))
return sim
print('Evaluating the Gaussian Kernel ...')
x1 = np.array([1, 2, 1])
x2 = np.array([0, 4, -1])
sigma = 2
sim = gaussianKernel(x1, x2, sigma)
print('Gaussian Kernel between x1 = [1; 2; 1], x2 = [0; 4; -1], sigma = 0.5 :\
\t%f\n(this value should be about 0.324652)' % sim)
_ = input('Press [Enter] to continue.')
# =============== Part 4: Visualizing Dataset 2 ================
print('Loading and Visualizing Data ...')
datainfo = sio.loadmat('ex6data2.mat')
X = datainfo['X']
Y = datainfo['y'][:, 0]
plotData(X, Y)
plt.show()
_ = input('Press [Enter] to continue.')
# ========== Part 5: Training SVM with RBF Kernel (Dataset 2) ==========
# 绘制边界
def visualBoundary(x, y, model):
plotData(x, y)
x1plot = np.linspace(np.min(x[:, 0]), np.max(x[:, 0]), 100)
x2plot = np.linspace(np.min(x[:, 1]), np.max(x[:, 1]), 100)
x1, x2 = np.meshgrid(x1plot, x2plot)
vals = np.zeros(np.shape(x1))
for i in range(np.size(x1, 1)):
this_x = np.vstack((x1[:, i], x2[:, i])).T
vals[:, i] = model.predict(this_x)
plt.contour(x1, x2, vals, levels=[0], colors='b')
# 这里需要注意的是gamma并不是原本中的sigma
print('Training SVM with RBF Kernel (this may take 1 to 2 minutes) ...')
c = 1; sigma = 0.1
gam = 1/(2*sigma**2)
clf = svm.SVC(kernel='rbf', C=1.0, gamma=gam)
clf.fit(X, Y)
visualBoundary(X, Y, clf)
plt.show()
_ = input('Press [Enter] to continue.')
# =============== Part 6: Visualizing Dataset 3 ================
datainfo = sio.loadmat('ex6data3.mat')
X = datainfo['X']
Y = datainfo['y'][:, 0]
Xval = datainfo['Xval']
Yval = datainfo['yval'][:, 0]
plotData(X, Y)
plt.show()
_ = input('Press [Enter] to continue.')
# ========== Part 7: Training SVM with RBF Kernel (Dataset 3) ==========
# 参数选择
def dataset3Params(x, y, xval, yval):
c = 1; sigma = 0.3
err_best = np.size(yval, 0)
c_choice = [0.3, 1.0]
sigma_choice = [0.1, 0.3]
for i in range(len(c_choice)):
for j in range(len(sigma_choice)):
clf = svm.SVC(C=c_choice[i], gamma=1/(2*sigma_choice[j]**2))
clf.fit(x, y)
pred = clf.predict(xval)
err = np.sum(pred != yval)/np.size(yval, 0)
if err_best>err:
err_best = err
c = c_choice[i]
sigma = sigma_choice[j]
return c, sigma
c, sigma = dataset3Params(X, Y, Xval, Yval)
clf = svm.SVC(C=c, gamma=1/(2*sigma**2))
clf = clf.fit(X, Y)
visualBoundary(X, Y, clf)
plt.show()

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -0,0 +1,137 @@
import numpy as np
import matplotlib.pylab as plt
import scipy.io as sio
import matplotlib.colors as pltcolor
# ================= Part 1: Find Closest Centroids ====================
# 找出最邻近点
def findCloseCenter(x, center):
k, l = np.shape(center)
xtemp = np.tile(x, k)
centertemp = center.flatten()
xtemp = np.power(xtemp-centertemp, 2)
xt = np.zeros((np.size(xtemp, 0), k))
for i in range(k):
for j in range(l):
xt[:, i] = xt[:, i]+xtemp[:, i*l+j]
idx = np.argmin(xt, 1)+1
return idx
print('Finding closest centroids.')
datainfo = sio.loadmat('ex7data2.mat')
X = datainfo['X']
K = 3
init_center = np.array([[3, 3], [6, 2], [8, 5]])
idX = findCloseCenter(X, init_center)
print('Closest centroids for the first 3 examples: ', idX[0:3])
print('(the closest centroids should be 1, 3, 2 respectively)')
_ = input('Press [Enter] to continue.')
# ===================== Part 2: Compute Means =========================
# 找出中心点
def computeCenter(x, idx, k):
m, n = np.shape(x)
center = np.zeros((k, n))
for i in range(k):
pos = np.where(idx == i+1)
center[i, :] = np.sum(x[pos], 0)/np.size(x[pos], 0)
return center
print('Computing centroids means.')
center = computeCenter(X, idX, K)
print('Centroids computed after initial finding of closest centroids: ')
print(center)
print('the centroids should be: ')
print('[[ 2.428301 3.157924 ], [ 5.813503 2.633656 ], [ 7.119387 3.616684 ]]')
_ = input('Press [Enter] to continue.')
# =================== Part 3: K-Means Clustering ======================
# 中心点连线
def drawLine(p1, p2):
x = np.array([p1[0], p2[0]])
y = np.array([p1[1], p2[1]])
plt.plot(x, y)
# 绘制数据点
def plotDataPoints(x, idx, k):
colors = ['red', 'green', 'blue']
plt.scatter(x[:, 0], x[:, 1], c=idx, cmap=pltcolor.ListedColormap(colors), s=40)
# 绘制中心点
def plotProgresskMeans(x, center, previous, idx, k, i):
plotDataPoints(x, idx, k)
plt.plot(center[:, 0], center[:, 1], 'x', ms=10, mew=1)
for j in range(np.size(center, 0)):
drawLine(center[j, :], previous[j, :])
plt.title('Iteration number %d' % (i+1))
# k均值聚类
def runkMeans(x, init_center, max_iter, plot_progress=False):
m, n = np.shape(x)
k = np.size(init_center, 0)
center = init_center
previous_center = center
idx = np.zeros((m,))
if plot_progress:
plt.ion()
fig = plt.figure()
for i in range(max_iter):
print('K-Means iteration %d/%d...' % (i+1, max_iter))
idx = findCloseCenter(x, center)
if plot_progress:
plotProgresskMeans(x, center, previous_center, idx, k, i)
previous_center = center
fig.canvas.draw()
_ = input('Press [Enter] to continue.')
center = computeCenter(x, idx, k)
plt.show(block=True)
plt.ioff()
return center, idx
print('Running K-Means clustering on example dataset.')
max_iter = 10
K = 3
init_center = np.array([[3, 3], [6, 2], [8, 5]])
center, idx = runkMeans(X, init_center, max_iter, True)
print('K-Means Done.')
_ = input('Press [Enter] to continue.')
# ============= Part 4: K-Means Clustering on Pixels ===============
# 生成初始点
def kMeansInitCenter(x, k):
randidx = np.random.permutation(np.size(x, 0))
center = x[randidx[0: k], :]
return center
print('Running K-Means clustering on pixels from an image.')
A = plt.imread('bird_small.png')
m, n, l = np.shape(A)
A_x = np.reshape(A, (m*n, l))
A_k = 16
A_max_iter = 10
A_init_center = kMeansInitCenter(A_x, A_k)
A_center, A_idx = runkMeans(A_x, A_init_center, A_max_iter)
print(A_center)
_ = input('Press [Enter] to continue.')
# ================= Part 5: Image Compression ======================
print('Applying K-Means to compress an image.')
A_idx = findCloseCenter(A_x, A_center)
X_recovered = A_center[A_idx-1, :]
X_back = X_recovered.reshape(m, n, l)
fig = plt.figure()
ax1 = fig.add_subplot(121)
ax1.imshow(A)
ax1.set_title('Original')
ax2 = fig.add_subplot(122)
ax2.imshow(X_back)
ax2.set_title('Compressed, with %d colors.' % A_k)
plt.show(block=False)
_ = input('Press [Enter] to continue.')

View File

@@ -0,0 +1,212 @@
import numpy as np
import matplotlib.pylab as plt
import scipy.io as sio
import numpy.linalg as la
import math
import matplotlib.cm as cm
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.colors as pltcolor
# ================== Part 1: Load Example Dataset ===================
print('Visualizing example dataset for PCA.')
datainfo = sio.loadmat('ex7data1.mat')
X = datainfo['X']
plt.plot(X[:, 0], X[:, 1], 'bo')
plt.axis([0.5, 6.5, 2, 8])
plt.axis('equal')
_ = input('Press [Enter] to continue.')
# =============== Part 2: Principal Component Analysis ===============
# 归一化
def featureNormalize(x):
mu = np.mean(x, 0)
sigma = np.std(x, 0, ddof=1)
x_norm = (x-mu)/sigma
return x_norm, mu, sigma
# pca
def pca(x):
m, n = np.shape(x)
sigma = 1/m*x.T.dot(x)
u, s, _ = la.svd(sigma)
return u, s
# 两点连线
def drawLine(p1, p2, lc='k-', lwidth=2):
x = np.array([p1[0], p2[0]])
y = np.array([p1[1], p2[1]])
plt.plot(x, y, lc, lw=lwidth)
print('Running PCA on example dataset.')
x_norm, mu, sigma = featureNormalize(X)
u, s = pca(x_norm)
drawLine(mu, mu+1.5*s[0]*u[:, 0])
drawLine(mu, mu+1.5*s[1]*u[:, 1])
plt.show()
print('Top eigenvector: ')
print(' U(:,1) = %f %f ' %(u[0, 0], u[1, 0]))
print('(you should expect to see -0.707107 -0.707107)')
_ = input('Press [Enter] to continue.')
# =================== Part 3: Dimension Reduction ===================
# 映射数据
def projectData(x, u, k):
z = x.dot(u[:, 0:k])
return z
# 数据复原
def recoverData(z, u, k):
x_rec = np.asmatrix(z).dot(u[:, 0:k].T)
return np.asarray(x_rec)
print('Dimension reduction on example dataset.')
plt.plot(x_norm[:, 0], x_norm[:, 1], 'bo')
plt.axis([-4, 3, -4, 3])
plt.axis('equal')
k = 1
z = projectData(x_norm, u, k)
print('Projection of the first example: ', z[0])
print('(this value should be about 1.481274)')
x_rec = recoverData(z, u, k)
print('Approximation of the first example: %f %f' % (x_rec[0, 0], x_rec[0, 1]))
plt.plot(x_rec[:, 0], x_rec[:, 1], 'ro')
for i in range(np.size(x_norm, 0)):
drawLine(x_norm[i, :], x_rec[i, :], 'k--', 1)
plt.show()
_ = input('Press [Enter] to continue.')
# =============== Part 4: Loading and Visualizing Face Data =============
# 显示数据
def displayData(x):
width = round(math.sqrt(np.size(x, 1)))
m, n = np.shape(x)
height = int(n/width)
# 显示图像的数量
drows = math.floor(math.sqrt(m))
dcols = math.ceil(m/drows)
pad = 1
# 建立一个空白“背景布”
darray = -1*np.ones((pad+drows*(height+pad), pad+dcols*(width+pad)))
curr_ex = 0
for j in range(drows):
for i in range(dcols):
if curr_ex >= m:
break
max_val = np.max(np.abs(x[curr_ex, :]))
darray[pad+j*(height+pad):pad+j*(height+pad)+height, pad+i*(width+pad):pad+i*(width+pad)+width]\
= x[curr_ex, :].reshape((height, width))/max_val
curr_ex += 1
if curr_ex >= m:
break
plt.imshow(darray.T, cmap='gray')
print('Loading face dataset.')
datainfo = sio.loadmat('ex7faces.mat')
X = datainfo['X']
displayData(X[0:100, :])
plt.show()
_ = input('Press [Enter] to continue.')
# =========== Part 5: PCA on Face Data: Eigenfaces ===================
print('Running PCA on face dataset\n(this mght take a minute or two ...)')
x_norm, mu, sigma = featureNormalize(X)
u, s = pca(x_norm)
displayData(u[:, 0:36].T)
plt.show()
_ = input('Press [Enter] to continue.')
# ============= Part 6: Dimension Reduction for Faces =================
print('Dimension reduction for face dataset.')
K = 100
Z = projectData(x_norm, u, K)
print('the project data Z has a size of ', np.shape(Z))
_ = input('Press [Enter] to continue.')
# ==== Part 7: Visualization of Faces after PCA Dimension Reduction ====
print('Visualizing the projected (reduced dimension) faces.')
X_rec = recoverData(Z, u, K)
fig = plt.figure()
plt.subplot(121)
displayData(x_norm[0:100, :])
plt.title('Original faces')
plt.subplot(122)
displayData(X_rec[0:100, :])
plt.title('Recovered faces')
plt.show()
_ = input('Press [Enter] to continue.')
# === Part 8(a): Optional (ungraded) Exercise: PCA for Visualization ===
# 生成初始点
def kMeansInitCenter(x, k):
randidx = np.random.permutation(np.size(x, 0))
center = x[randidx[0: k], :]
return center
# 找出最邻近点
def findCloseCenter(x, center):
k, l = np.shape(center)
xtemp = np.tile(x, k)
centertemp = center.flatten()
xtemp = np.power(xtemp-centertemp, 2)
xt = np.zeros((np.size(xtemp, 0), k))
for i in range(k):
for j in range(l):
xt[:, i] = xt[:, i]+xtemp[:, i*l+j]
idx = np.argmin(xt, 1)+1
return idx
# 找出中心点
def computeCenter(x, idx, k):
m, n = np.shape(x)
center = np.zeros((k, n))
for i in range(k):
pos = np.where(idx == i+1)
center[i, :] = np.sum(x[pos], 0)/np.size(x[pos], 0)
return center
# k均值聚类
def runkMeans(x, init_center, max_iter):
m, n = np.shape(x)
k = np.size(init_center, 0)
center = init_center
idx = np.zeros((m,))
for i in range(max_iter):
idx = findCloseCenter(x, center)
center = computeCenter(x, idx, k)
return center, idx
A = plt.imread('bird_small.png')
img_size = np.shape(A)
X = A.reshape(img_size[0]*img_size[1], img_size[2])
K = 16
max_iter = 10
init_center = kMeansInitCenter(X, K)
center, idx = runkMeans(X, init_center, max_iter)
sel = np.floor(np.random.random((1000,))*np.size(X, 0)).astype(int)+1
colors = cm.rainbow(np.linspace(0, 1, K))
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(X[sel, 0], X[sel, 1], X[sel, 2], c=idx[sel], cmap=pltcolor.ListedColormap(colors), marker='o')
ax.set_title('Pixel dataset plotted in 3D. Color shows centroid memberships')
plt.show()
_ = input('Press [Enter] to continue.')
# === Part 8(b): Optional (ungraded) Exercise: PCA for Visualization ===
X_norm, mu, sigma = featureNormalize(X)
u, s = pca(X_norm)
Z = projectData(X_norm, u, 2)
colors = cm.rainbow(np.linspace(0, 1, K))
plt.scatter(Z[sel, 0], Z[sel, 1], c=idx[sel], cmap=pltcolor.ListedColormap(colors), marker='o')
plt.title('Pixel dataset plotted in 2D, using PCA for dimensionality reduction')
plt.show()
_ = input('Press [Enter] to continue.')

View File

@@ -0,0 +1,102 @@
import numpy as np
import matplotlib.pylab as plt
import scipy.io as sio
import math
import scipy.linalg as la
from mpl_toolkits.mplot3d import Axes3D
# ================== Part 1: Load Example Dataset ===================
print('Visualizing example dataset for outlier detection.')
datainfo = sio.loadmat('ex8data1.mat')
X = datainfo['X']
Xval = datainfo['Xval']
Yval = datainfo['yval'][:, 0]
plt.plot(X[:, 0], X[:, 1], 'bx')
plt.axis([0, 30, 0, 30])
plt.xlabel('Latency (ms)')
plt.ylabel('Throughput (mb/s)')
plt.show()
_ = input('Press [Enter] to continue.')
# ================== Part 2: Estimate the dataset statistics ===================
# 高斯估计
def estimateGauss(x):
m, n = x.shape
mu = np.sum(x, 0)/m
sigma = np.sum(np.power(x-mu, 2), 0)/m
return mu, sigma
# 多变量高斯估计
def multivariateGaussian(x, mu, sigma2):
k = np.size(mu, 0)
sigma2 = np.diag(sigma2)
x = x-mu
p = (2*math.pi)**(-k/2)*la.det(sigma2)**(-0.5)*np.exp(-0.5*np.sum(x.dot(la.pinv(sigma2))*x, 1))
return p
# 观测拟合效果
def visualFit(x, mu, sigma2):
temp = np.arange(0, 35, 0.5)
x1, x2 = np.meshgrid(temp, temp)
z = multivariateGaussian(np.vstack((x1.flatten(), x2.flatten())).T, mu, sigma2)
z = z.reshape(x1.shape)
plt.plot(x[:, 0], x[:, 1], 'bx')
plt.contour(x1, x2, z, np.power(10.0, np.arange(-20, 0, 3)))
plt.xlabel('Latency (ms)')
plt.ylabel('Throughput (mb/s)')
print('Visualizing Gaussian fit.')
mu, sigma2 = estimateGauss(X)
p = multivariateGaussian(X, mu, sigma2)
visualFit(X, mu, sigma2)
# plt.show()
_ = input('Press [Enter] to continue.')
# ================== Part 3: Find Outliers ===================
def selectThreshold(yval, pval):
bestEpsilon = 0.0
bestF1 = 0.0
F1 = 0.0
stepsize = (np.max(pval)-np.min(pval))/1000
arrlist = np.arange(np.min(pval), np.max(pval), stepsize).tolist()
for epsilon in arrlist:
tp = np.sum(np.logical_and(pval < epsilon, yval == 1))
fp = np.sum(np.logical_and(pval < epsilon, yval == 0))
fn = np.sum(np.logical_and(pval >= epsilon, yval == 1))
if tp+fp == 0 or tp+fn == 0:
F1 = -1
else:
prec = tp/(tp+fp)
rec = tp/(tp+fn)
F1 = 2*prec*rec/(prec+rec)
if F1 > bestF1:
bestF1 = F1
bestEpsilon = epsilon
return bestF1, bestEpsilon
pval = multivariateGaussian(Xval, mu, sigma2)
F1, epsilon = selectThreshold(Yval, pval)
print('Best epsilon found using cross-validation: ', epsilon)
print('Best F1 on Cross Validation Set: ', F1)
print('(you should see a value epsilon of about 8.99e-05)')
outliers = np.where(p < epsilon)
plt.plot(X[outliers, 0], X[outliers, 1], 'o', mfc='none', ms=8, mec='r')
plt.show()
_ = input('Press [Enter] to continue.')
# ================== Part 4: Multidimensional Outliers ===================
datainfo = sio.loadmat('ex8data2.mat')
X = datainfo['X']
Xval = datainfo['Xval']
Yval = datainfo['yval'][:, 0]
mu, sigma2 = estimateGauss(X)
p = multivariateGaussian(X, mu, sigma2)
pval = multivariateGaussian(Xval, mu, sigma2)
F1, epsilon = selectThreshold(Yval, pval)
print('Best epsilon found using cross-validation: ', epsilon)
print('Best F1 on Cross Validation Set: ', F1)
print('# Outliers found: ', np.sum(p < epsilon))
print('(you should see a value epsilon of about 1.38e-18)')

View File

@@ -0,0 +1,206 @@
import numpy as np
import matplotlib.pylab as plt
import scipy.io as sio
import scipy.linalg as la
import scipy.optimize as op
# =============== Part 1: Loading movie ratings dataset ================
print('Loading movie ratings dataset.')
datainfo = sio.loadmat('ex8_movies.mat')
Y = datainfo['Y']
R = datainfo['R'].astype('bool') # 1682x943
print('Average rating for movie 1 (Toy Story): %f / 5' % np.mean(Y[0, R[0, :]], 0))
plt.imshow(Y, extent=[0, 1000, 0, 1700], aspect='auto')
plt.xlabel('Movies')
plt.ylabel('Users')
plt.show()
_ = input('Press [Enter] to continue.')
# ============ Part 2: Collaborative Filtering Cost Function ===========
# 计算损失函数
def cofiCostFunc(params, Y, R, num_users, num_movies, num_features, lam):
X = np.reshape(params[0: num_movies*num_features], (num_movies, num_features))
Theta = np.reshape(params[num_movies*num_features:], (num_users, num_features))
J = 1/2*np.sum(R*(X.dot(Theta.T)-Y)**2)+lam/2*(np.sum(Theta**2)+np.sum(X**2))
return J
# 计算梯度函数
def cofiGradFunc(params, Y, R, num_users, num_movies, num_features, lam):
X = np.reshape(params[0: num_movies * num_features], (num_movies, num_features))
Theta = np.reshape(params[num_movies * num_features:], (num_users, num_features))
X_grad = np.zeros(X.shape)
Theta_grad = np.zeros(Theta.shape)
for i in range(np.size(X, 0)):
idx = R[i, :] == 1
X_grad[i, :] = (X[i, :].dot(Theta[idx, :].T)-Y[i, idx]).dot(Theta[idx, :])+lam*X[i, :]
for j in range(np.size(Theta, 0)):
jdx = R[:, j] == 1
Theta_grad[j, :] = (Theta[j, :].dot(X[jdx, :].T)-Y[jdx, j].T).dot(X[jdx, :])+lam*Theta[j, :]
grad = np.hstack((X_grad.flatten(), Theta_grad.flatten()))
return grad
datainfo2 = sio.loadmat('ex8_movieParams.mat')
X = datainfo2['X']
Theta = datainfo2['Theta']
num_users = datainfo2['num_users']
num_movies = datainfo2['num_movies']
num_features = datainfo2['num_features']
# 以少数据量实验
num_users = 4; num_movies = 5; num_features = 3
X = X[0:num_movies, 0:num_features]
Theta = Theta[0:num_users, 0:num_features]
Y = Y[0:num_movies, 0:num_users]
R = R[0:num_movies, 0:num_users]
params = np.hstack((X.flatten(), Theta.flatten()))
J = cofiCostFunc(params, Y, R, num_users, num_movies, num_features, 0)
Grad = cofiGradFunc(params, Y, R, num_users, num_movies, num_features, 0)
print('Cost at loaded parameters: %f \n(this value should be about 22.22)' % J)
_ = input('Press [Enter] to continue.')
# ============== Part 3: Collaborative Filtering Gradient ==============
# 计算数值梯度
def computeNumericalGradient(func, extraArgs, theta):
numgrad = np.zeros(theta.shape)
perturb = np.zeros(theta.shape)
episilon = 1e-4
for p in range(np.size(theta, 0)):
perturb[p] = episilon
loss1 = func(theta-perturb, *extraArgs)
loss2 = func(theta+perturb, *extraArgs)
numgrad[p] = (loss2-loss1)/(2*episilon)
perturb[p] = 0
return numgrad
# 检查梯度
def checkCostFunc(lamb=0):
X_t = np.random.random((4, 3))
Theta_t = np.random.random((5, 3))
Y = X_t.dot(Theta_t.T)
Y[np.random.random(Y.shape) > 0.5] = 0
R = np.zeros(Y.shape)
R[Y != 0] = 1
X = np.random.randn(X_t.shape[0], X_t.shape[1])
Theta = np.random.randn(Theta_t.shape[0], Theta_t.shape[1])
num_users = np.size(Y, 1)
num_movies = np.size(Y, 0)
num_features = np.size(Theta_t, 1)
params = np.hstack((X.flatten(), Theta.flatten()))
numgrad = computeNumericalGradient(cofiCostFunc, (Y, R, num_users, num_movies, num_features, lamb), params)
grad = cofiGradFunc(params, Y, R, num_users, num_movies, num_features, lamb)
print('Numerical: ', numgrad)
print('Analytical: ', grad)
print('The above two columns you get should be very similar.')
print('(Left-Your Numerical Gradient, Right-Analytical Gradient)')
diff = la.norm(numgrad-grad)/la.norm(numgrad+grad)
print('If your backpropagation implementation is correct, ')
print('then the relative difference will be small (less than 1e-9).')
print('Relative Difference: ', diff)
print('Checking Gradients (without regularization) ...')
checkCostFunc()
_ = input('Press [Enter] to continue.')
# ========= Part 4: Collaborative Filtering Cost Regularization ========
params = np.hstack((X.flatten(), Theta.flatten()))
J = cofiCostFunc(params, Y, R, num_users, num_movies, num_features, 1.5)
print('Cost at loaded parameters (lambda = 1.5): %f\n(this value should be about 31.34)' %J)
_ = input('Press [Enter] to continue.')
# ======= Part 5: Collaborative Filtering Gradient Regularization ======
print('Checking Gradients (with regularization) ...')
checkCostFunc(1.5)
_ = input('Press [Enter] to continue.')
# ============== Part 6: Entering ratings for a new user ===============
# 加载电影数据
def loadMovieList():
movieList = [line.split(' ', 1)[1] for line in open('movie_ids.txt', encoding='utf8')]
return movieList
movieList = loadMovieList()
my_ratings = np.zeros((1682,))
# For example, Toy Story (1995) has ID 1, so to rate it "4", you can set
my_ratings[0] = 4
my_ratings[97] = 2
my_ratings[6] = 3
my_ratings[11] = 5
my_ratings[53] = 4
my_ratings[63] = 5
my_ratings[65] = 3
my_ratings[68] = 5
my_ratings[182] = 4
my_ratings[225] = 5
my_ratings[354] = 5
print('New user ratings:')
for i in range(np.size(my_ratings, 0)):
if my_ratings[i] > 0:
print('Rated %d for %s' %(my_ratings[i], movieList[i]))
_ = input('Press [Enter] to continue.')
# ================== Part 7: Learning Movie Ratings ====================
# 归一化
def normalizeRating(Y, R):
m, n = Y.shape
Ymean = np.zeros((m,))
Ynorm = np.zeros(Y.shape)
for i in range(m):
idx = R[i, :] == 1
Ymean[i] = np.mean(Y[i, idx])
Ynorm[i, idx] = Y[i, idx]-Ymean[i]
return Ynorm, Ymean
print('Training collaborative filtering...')
datainfo3 = sio.loadmat('ex8_movies.mat')
Y = datainfo3['Y']
R = datainfo3['R']
Y = np.c_[my_ratings.reshape((np.size(my_ratings, 0), 1)), Y]
R = np.c_[(my_ratings!=0).reshape((np.size(my_ratings, 0), 1)), R]
Y_norm, Ymean = normalizeRating(Y, R)
num_users = np.size(Y, 1)
num_movies = np.size(Y, 0)
num_features = 10
X = np.random.randn(num_movies, num_features)
Theta = np.random.randn(num_users, num_features)
init_params = np.hstack((X.flatten(), Theta.flatten()))
lamb = 10
theta = op.fmin_cg(cofiCostFunc, init_params, fprime=cofiGradFunc, \
args=(Y, R, num_users, num_movies, num_features, lamb), maxiter=100)
X = np.reshape(theta[0: num_movies*num_features], (num_movies, num_features))
Theta = np.reshape(theta[num_movies*num_features:], (num_users, num_features))
print('Recommender system learning completed.')
_ = input('Press [Enter] to continue.')
# ================== Part 8: Recommendation for you ====================
p = X.dot(Theta.T)
my_pred = p[:, 0]+Ymean
ix = np.argsort(my_pred)[::-1]
print('Top recommendations for you:')
for i in range(10):
j = ix[i]
print('Predicting rating %.1f for movie %s' %(my_pred[j], movieList[j]))
print('Original ratings provided:')
for i in range(np.size(my_ratings, 0)):
if my_ratings[i] > 0:
print('Rated %d for %s' %(my_ratings[i], movieList[i]))

View File

@@ -0,0 +1,22 @@
# ML-Andrew-Ng
## 主要内容
Coursera上Andrew Ng的《机器学习》课程编程作业,采用python来完成
## 进度
Exercise1:Linear Regression
Exercise2:Logistic Regression
Exercise3:Multi-class Classification and Neural Networks
Exercise4:Neural Networks Learning
Exercise5:Regularized Linear Regression and Bias v.s. Variance
Exercise6:Support Vector Machines
Exercise7:K-means Clustering and Principal Component Analysis
Exercise8:Anomaly Detection and Recommender Systems

View File

@@ -0,0 +1,343 @@
# Keras2.0
#### Why Keras
你可能会问,为什么不学TensorFlow呢?明明tensorflow才是目前最流行的machine learning库之一啊。其实,它并没有那么好用,tensorflow和另外一个功能相近的toolkit theano,它们是非常flexible的,你甚至可以把它想成是一个微分器,它完全可以做deep learning以外的事情,因为它的作用就是帮你算微分,拿到微分之后呢,你就可以去算gradient descent之类,而这么flexible的toolkit学起来是有一定的难度的,你没有办法在半个小时之内精通这个toolkit
但是另一个toolkit——Keras,你是可以在数十分钟内就熟悉并精通它的,然后用它来implement一个自己的deep learning,Keras其实是tensorflow和theano的interface,所以用Keras就等于在用tensorflow,只是有人帮你把操纵tensorflow这件事情先帮你写好
所以Keras是比较容易去学习和使用的,并且它也有足够的弹性,除非你自己想要做deep learning的研究,去设计一个自己的network,否则多数你可以想到的network,在Keras里都有现成的function可以拿来使用;因为它背后就是tensorflow or theano,所以如果你想要精进自己的能力的话,你永远可以去改Keras背后的tensorflow的code,然后做更厉害的事情
而且,现在Keras已经成为了Tensorflow官方的API,它像搭积木一样简单
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/keras.png" width="50%;" /></center>
接下来我们用手写数字识别的demo来介绍一下"Hello world" of deep learning
#### prepare data
使用的data是MNIST的Data:http://yann.lecun.com/exdb/mnist/
Keras提供了自动下载MNIST data的function:http://keras.io/datasets/
#### process
首先要先导入keras包:`from keras.models import Sequential`
##### step 1:define a set of function——neural network
先用`Sequential()`宣告建立一个model
~~~python
model = Sequential()
~~~
然后开始叠一个neural network:它有两个hidden layer,每个hidden layer都有500个neuron
- 加一个**Fully connected**的layer——用**Dense**来表示,当然你也可以加别的layer,比如convolution的layer
之前我们说过,input layer比较特殊,它并不是真正意义上的layer,因为它没有所谓的"neuron",于是Keras在model里面加的第一层layer会有一些特殊,要求同时输入`input_dim`和`units`,分别代表第一层hidden layer输入维数(也就是input layer的dimension)和第一层hidden layer的neuron个数
`input_dim=28*28`表示一个28*28=784长度的vector,代表image;`units=500`表示该层hidden layer要有500个neuron;`activation=‘sigmoid’`表示激活函数使用sigmoid function
~~~python
model.add(Dense(input_dim=28 * 28, units=500, activation='sigmoid'))
~~~
加完layer之后,还需要设定该层hidden layer所使用的activation function,这里直接就用sigmoid function
在Keras里还可以选别的activation function,比如softplus、softsign、relu、tanh、hard_sigmoid、linear等等,如果你要加上自己的activation function,其实也蛮容易的,只要在Keras里面找到写activation function的地方,自己再加一个进去就好了
- 从第二层hidden layer开始,如果要在model里再加一个layer,就用model.add增加一个Dense全连接层,包括`units`和`activation`参数
这边就不需要再redefine `input_dim`是多少了,因为新增layer的input就等于前一个layer的output,Keras自己是知道这件事情的,所以你就直接告诉它说,新加的layer有500个neuron就好了
这里同样把activation function设置为sigmoid function
~~~python
model.add(Dense(units=500, activation='sigmoid'))
~~~
- 最后,由于是分10个数字,所以output是10维,如果把output layer当做一个Multi-class classifier的话,那activation function就用softmax(这样可以让output每一维的几率之和为1,表现得更像一个概率分布),当然你也可以选择别的
~~~python
model.add(Dense(units=10, activation='softmax'))
~~~
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/keras-step1.png" width="60%;" /></center>
注:上图中写的是Keras1.0的语法,在笔记中给出的则是Keras2.0的语法,应当使用后者
##### Step 2:goodness of function——cross entropy
evaluate一个function的好坏,你要做的事情是用model.compile去定义你的loss function是什么
比如说你要用**cross entropy**的话,那你的loss参数就是**categorical_crossentropy**(Keras里的写法)
~~~python
model.compile(loss='categorical_crossentropy',
optimizer='adam',
metrics=['accuracy'])
~~~
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/keras-step2.png" width="60%;" /></center>
##### Step 3:pick the best function
###### Configuration
在training之前,你要先下一些**configuration**告诉它training的时候,你打算要怎么做
你要定义的第一个东西是optimizer,也就是说,你要用什么样的方式来找最好的function,虽然optimizer后面可以接不同的方式,但是这些不同的方式,其实都是gradient descent类似的方法
有一些方法machine会自动地,empirically(根据经验地)决定learning rate的值应该是多少,所以这些方法是不需要给它learning rate的,Keras里面有诸如:SGD(gradient descent)、RMSprop、Adagrad、Adadelta、Adam、Adamax、Nadam之类的寻找最优参数的方法,它们都是gradient descent的方式
~~~python
model.compile(loss='categorical crossentropy',
optimizer='adam',
metrics=['accuracy'])
~~~
###### Training
决定好怎么做gradient descent之后,就是实际去做训练了,去跑gradient descent找最优参数了
这里使用的是`model.fit`方法,要给它4给input(假设我们给了10000张image作Training data)
- 第一个input是Training data——`x_train`
在这个case里,Training data就是一张一张的image,需要把它存放到numpy array里面,这个numpy array是two-dimension的matrix,每张image存为numpy array的一个行向量(它把image中28\*28个像素值拉成一个行向量),总共有10000行,它的列数就是每张image的像素点个数,即28\*28=784列
- 第二个input是每一个Training data对应的label——`y_train`
在这个case里,就是标志着这张image对应的是0~9的那一个数字,同样也是two-dimension的numpy array,每张image的label存为numpy array的一个行向量,用来表示0~9这10个数字中的某一个数,所以是10列,用的是one-hot编码,10个数字中对了对应image的那个数字为1之外其余都是0
- 第三个input是`batch_size`,告诉Keras我们的batch要有多大
在这个case里,batch_size=100,表示我们要把100张随机选择的image放到一个batch里面,然后把所有的image分成一个个不同的batch,Keras会自动帮你完成随机选择image的过程,不需要自己去code
- 第四个input是`nb_epoch`,表示对所有batch的训练要做多少次
在这个case里,nb_epoch=20,表示要对所有的batch进行20遍gradient descent的训练,每看到一个batch就update一次参赛,假设现在每一个epoch里面有100个batch,就对应着update 100次参数,20个epoch就是update 2000次参数
注:如果batch_size设为1,就是**Stochastic Gradient Descent**(随机梯度下降法),这个我们之前在讨论gradient descent的时候有提到,就是每次拿到一个样本点就update一次参数,而不是每次拿到一批样本点的error之后才去update参数,因此stochastic gradient descent的好处是它的速度比较快,虽然每次update参数的方向是不稳定的,但是**天下武功,唯快不破**,在别人出一拳的时候,它就已经出了100拳了,所以它是比较强的
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/keras-step3.png" width="60%;" /></center>
#### Mini-batch
这里有一个秘密,就是我们在做deep learning的gradient descent的时候,并不会真的去minimize total loss,那我们做的是什么呢?我们会把Training data分成一个一个的batch,比如说你的Training data一共有1w张image,每次random选100张image作为一个batch(我的理解是,先将原来的image分布随机打乱,然后再按顺序每次挑出batch_size张image组成一个batch,这样才能保证所有的data都有被用到,且不同的batch里不会出现重复的data)
- 像gradient descent一样,先随机initialize network的参数
- 选第一个batch出来,然后计算这个batch里面的所有element的total loss,$L'=l^1+l^{31}+...$,接下来根据$L'$去update参数,也就是计算$L'$对所有参数的偏微分,然后update参数
注意:不是全部data的total loss
- 再选择第二个batch,现在这个batch的total loss是$L''=l^2+l^{16}+...$,接下来计算$L''$对所有参数的偏微分,然后update参数
- 反复做这个process,直到把所有的batch通通选过一次,所以假设你有100个batch的话,你就把这个参数update 100次,把所有batch看过一次,就叫做一个epoch
- 重复epoch的过程,所以你在train network的时候,你会需要好几十个epoch,而不是只有一个epoch
整个训练的过程类似于stochastic gradient descent,不是将所有数据读完才开始做gradient descent的,而是拿到一部分数据就做一次gradient descent
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/mini-batch.png" width="50%;" /></center>
#### Batch size and Training Speed
##### batch size太小会导致不稳定,速度上也没有优势
前面已经提到了,stochastic gradient descent速度快,表现好,既然如此,为什么我们还要用Mini-batch呢?这就涉及到了一些实际操作上的问题,让我们必须去用Mini-batch
举例来说,我们现在有50000个examples,如果我们把batch size设置为1,就是stochastic gradient descent,那在一个epoch里面,就会update 50000次参数;如果我们把batch size设置为10,在一个epoch里面,就会update 5000次参数
看上去stochastic gradient descent的速度貌似是比较快的,它一个epoch更新参数的次数比batch size等于10的情况下要快了10倍,但是!我们好像忽略了一个问题,我们之前一直都是下意识地认为不同batch size的情况下运行一个epoch的时间应该是相等的,然后我们才去比较每个epoch所能够update参数的次数,可是它们又怎么可能会是相等的呢?
实际上,当你batch size设置不一样的时候,一个epoch需要的时间是不一样的,以GTX 980为例,下图是对总数为50000笔的Training data设置不同的batch size时,每一个epoch所需要花费的时间
- case1:如果batch size设为1,也就是stochastic gradient descent,一个epoch要花费166秒,接近3分钟
- case2:如果batch size设为10,那一个epoch是17秒
也就是说,当stochastic gradient descent算了一个epoch的时候,batch size为10的情况已经算了近10个epoch了;所以case1跑一个epoch,做了50000次update参数的同时,case2跑了十个epoch,做了近5000\*10=50000次update参数;你会发现batch size设1和设10,update参数的次数几乎是一样的
如果不同batch size的情况,update参数的次数几乎是一样的,你其实会想要选batch size更大的情况,就像在本例中,相较于batch size=1,你会更倾向于选batch size=10,因为batch size=10的时候,是会比较稳定的,因为**由更大的数据集计算的梯度能够更好的代表样本总体,从而更准确的朝向极值所在的方向**
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/batch-size-speed.png" width="50%;" /></center>
我们之前把gradient descent换成stochastic gradient descent,是因为后者速度比较快,update次数比较多,可是现在如果你用stochastic gradient descent并没有见得有多快,那你为什么不选一个update次数差不多,又比较稳定的方法呢?
##### batch size会受到GPU平行加速的限制,太大可能导致在train的时候卡住
上面例子的现象产生的原因是我们用了GPU,用了平行运算,所以batch size=10的时候,这10个example其实是同时运算的,所以你在一个batch里算10个example的时间跟算1个example的时间几乎可以是一样的
那你可能会问,既然batch size越大,它会越稳定,而且还可以平行运算,那为什么不把batch size变得超级大呢?这里有两个claim(声明):
- 第一个claim就是,如果你把batch size开到很大,最终GPU会没有办法进行平行运算,它终究是有自己的极限的,也就是说它同时考虑10个example和1个example的时间是一样的,但当它考虑10000个example的时候,时间就不可能还是跟一个example一样,因为batch size考虑到**硬件限制**,是没有办法无穷尽地增长的
- 第二个claim是说,如果把batch size设的很大,在train gradient descent的时候,可能跑两下你的network就卡住了,就陷到saddle point或者local minima里面去了
因为在neural network的error surface上面,如果你把loss的图像可视化出来的话,它并不是一个convex的optimization problem,不会像理想中那么平滑,实际上它会有很多的坑坑洞洞
如果你用的batch size很大,甚至是Full batch,那你走过的路径会是比较平滑连续的,可能这一条平滑的曲线在走向最低点的过程中就会在坑洞或是缓坡上卡住了;但是,如果你的batch size没有那么大,意味着你走的路线没有那么的平滑,有些步伐走的是**随机性**的,路径是会有一些曲折和波动的
可能在你走的过程中,它的曲折和波动刚好使得你“绕过”了那些saddle point或是local minima的地方;或者当你陷入不是很深的local minima或者没有遇到特别麻烦的saddle point的时候,它步伐的随机性就可以帮你跳出这个gradient接近于0的区域,于是你更有可能真的走向global minima的地方
而对于Full batch的情况,它的路径是没有随机性的,是稳定朝着目标下降的,因此在这个时候去train neural network其实是有问题的,可能update两三次参数就会卡住,所以mini batch是有必要的
下面是我手画的图例和注释:
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/batch-size.jpg" width="70%;" /></center>
##### 不同batch size在梯度下降上的表现
如下图,左边是full batch(拿全部的Training data做一个batch)的梯度下降效果,可以看到每一次迭代成本函数都呈现下降趋势,这是好的现象,说明我们w和b的设定一直再减少误差, 这样一直迭代下去我们就可以找到最优解;右边是mini batch的梯度下降效果,可以看到它是上下波动的,成本函数的值有时高有时低,但总体还是呈现下降的趋势, 这个也是正常的,因为我们每一次梯度下降都是在min batch上跑的而不是在整个数据集上, 数据的差异可能会导致这样的波动(可能某段数据效果特别好,某段数据效果不好),但没关系,因为它整体是呈下降趋势的
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/keras-gd1.png" width="50%;" /></center>
把下面的图看做是梯度下降空间:蓝色部分是full batch而紫色部分是mini batch,就像上面所说的mini batch不是每次迭代损失函数都会减少,所以看上去好像走了很多弯路,不过整体还是朝着最优解迭代的,而且由于mini batch一个epoch就走了5000步(5000次梯度下降),而full batch一个epoch只有一步,所以虽然mini batch走了弯路但还是会快很多
而且,就像之前提到的那样,mini batch在update的过程中,步伐具有随机性,因此紫色的路径可以在一定程度上绕过或跳出saddle point、local minima这些gradient趋近于0的地方;而蓝色的路径因为缺乏随机性,只能按照既定的方式朝着目标前进,很有可能就在中途被卡住,永远也跳不出来了
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/keras-gd2.png" width="40%;" /></center>
当然,就像之前讨论的一样,如果batch size太小,会造成速度不仅没有加快反而会导致下降的曲线更加不稳定的情况产生
==**因此batch size既不能太大,因为它会受到硬件GPU平行加速的限制,导致update次数过于缓慢,并且由于缺少随机性而很容易在梯度下降的过程中卡在saddle point或是local minima的地方(极端情况是Full batch);而且batch size也不能太小,因为它会导致速度优势不明显的情况下,梯度下降曲线过于不稳定,算法可能永远也不会收敛(极端情况是Stochastic gradient descent)**==
##### GPU是如何平行加速的
整个network,不管是Forward pass还是Backward pass,都可以看做是一连串的矩阵运算的结果
那今天我们就可以比较batch size等于1(stochastic gradient descent)和10(mini batch)的差别
如下图所示,stochastic gradient descent就是对每一个input x进行单独运算;而mini batch,则是把同一个batch里面的input全部集合起来,假设现在我们的batch size是2,那mini batch每一次运算的input就是把黄色的vector和绿色的vector拼接起来变成一个matrix,再把这个matrix乘上$w_1$,你就可以直接得到$z^1$和$z^2$
这两件事在理论上运算量是一样多的,但是在实际操作上,对GPU来说,在矩阵里面相乘的每一个element都是可以平行运算的,所以图中stochastic gradient descent运算的时间反而会变成下面mini batch使用GPU运算速度的两倍,这就是为什么我们要使用mini batch的原因
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/matrix-speed.png" width="50%;" /></center>
所以,如果你买了GPU,但是没有使用mini batch的话,其实就不会有多少加速的效果
#### Save and Load Models
Keras是可以帮你save和load model的,你可以把train好的model存起来,以后再用另外一个程式读出来,它也可以帮你做testing
那怎么用neural network去testing呢?有两种case:
- case 1是**evaluation**,比如今天我有一组testing set,testing set的答案也是已知的,那Keras就可以帮你算现在的正确率有多少,这个`model.evaluate`函数有两个input,就是testing的image和testing的label
~~~python
score = model.evaluate(x_test,y_test)
print('Total loss on Testing Set:',score[0])
print('Accuracy of Testing Set:',score[1])
~~~
- case 2是**prediction**,这个时候`model.predict`函数的input只有image data而没有任何的label data,output就直接是分类的结果
~~~python
result = model.predict(x_test)
~~~
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/save-load-model.png" width="60%;" /></center>
#### Appendix:手写数字识别完整代码(Keras2.0)
##### code
~~~python
import numpy as np
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation
from keras.layers import Conv2D, MaxPooling2D, Flatten
from keras.optimizers import SGD, Adam
from keras.utils import np_utils
from keras.datasets import mnist
# categorical_crossentropy
def load_data():
(x_train, y_train), (x_test, y_test) = mnist.load_data()
number = 10000
x_train = x_train[0:number]
y_train = y_train[0:number]
x_train = x_train.reshape(number, 28 * 28)
x_test = x_test.reshape(x_test.shape[0], 28 * 28)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
# convert class vectors to binary class matrices
y_train = np_utils.to_categorical(y_train, 10)
y_test = np_utils.to_categorical(y_test, 10)
x_train = x_train
x_test = x_test
# x_test=np.random.normal(x_test)
x_train = x_train / 255
x_test = x_test / 255
return (x_train, y_train), (x_test, y_test)
if __name__ == '__main__':
# load training data and testing data
(x_train, y_train), (x_test, y_test) = load_data()
# define network structure
model = Sequential()
model.add(Dense(input_dim=28*28, units=500, activation='sigmoid'))
model.add(Dense(units=500, activation='sigmoid'))
model.add(Dense(units=10, activation='softmax'))
# set configurations
model.compile(loss='categorical_crossentropy',
optimizer='adam', metrics=['accuracy'])
# train model
model.fit(x_train, y_train, batch_size=100, epochs=20)
# evaluate the model and output the accuracy
result = model.evaluate(x_test, y_test)
print('Test Acc:', result[1])
~~~
##### result
~~~python
Epoch 1/20
10000/10000 [==============================] - 2s 214us/step - loss: 1.1724 - acc: 0.6558
Epoch 2/20
10000/10000 [==============================] - 1s 146us/step - loss: 0.3847 - acc: 0.8964
Epoch 3/20
10000/10000 [==============================] - 1s 132us/step - loss: 0.2968 - acc: 0.9119
Epoch 4/20
10000/10000 [==============================] - 1s 146us/step - loss: 0.2535 - acc: 0.9268
Epoch 5/20
10000/10000 [==============================] - 2s 185us/step - loss: 0.2284 - acc: 0.9332
Epoch 6/20
10000/10000 [==============================] - 1s 141us/step - loss: 0.2080 - acc: 0.9369
Epoch 7/20
10000/10000 [==============================] - 1s 135us/step - loss: 0.1829 - acc: 0.9455
Epoch 8/20
10000/10000 [==============================] - 1s 135us/step - loss: 0.1617 - acc: 0.9520
Epoch 9/20
10000/10000 [==============================] - 1s 136us/step - loss: 0.1470 - acc: 0.9563
Epoch 10/20
10000/10000 [==============================] - 1s 133us/step - loss: 0.1340 - acc: 0.9607
Epoch 11/20
10000/10000 [==============================] - 1s 141us/step - loss: 0.1189 - acc: 0.9651
Epoch 12/20
10000/10000 [==============================] - 1s 143us/step - loss: 0.1056 - acc: 0.9696
Epoch 13/20
10000/10000 [==============================] - 1s 140us/step - loss: 0.0944 - acc: 0.9728
Epoch 14/20
10000/10000 [==============================] - 2s 172us/step - loss: 0.0808 - acc: 0.9773
Epoch 15/20
10000/10000 [==============================] - 1s 145us/step - loss: 0.0750 - acc: 0.9800
Epoch 16/20
10000/10000 [==============================] - 1s 134us/step - loss: 0.0643 - acc: 0.9826
Epoch 17/20
10000/10000 [==============================] - 1s 132us/step - loss: 0.0568 - acc: 0.9850
Epoch 18/20
10000/10000 [==============================] - 1s 135us/step - loss: 0.0510 - acc: 0.9873
Epoch 19/20
10000/10000 [==============================] - 1s 134us/step - loss: 0.0434 - acc: 0.9898
Epoch 20/20
10000/10000 [==============================] - 1s 134us/step - loss: 0.0398 - acc: 0.9906
10000/10000 [==============================] - 1s 79us/step
Test Acc: 0.9439
~~~
可以发现每次做完一个epoch的update后,手写数字识别的准确率都有上升,最终训练好的model识别准确率等于94.39%
注:把activation function从sigmoid换成relu可以使识别准确率更高,这里不再重复试验

View File

@@ -0,0 +1,462 @@
# Convolutional Neural network(part 1)
> CNN常常被用在影像处理上,它的theory base就是三个property,和两个架构
> convolution 架构:针对property 1和property 2
> max pooling架构:针对property 3
#### Why CNN for Image?
##### CNN V.s. DNN
我们当然可以用一般的neural network来做影像处理,不一定要用CNN,比如说,你想要做图像的分类,那你就去train一个neural network,它的input是一张图片,你就用里面的pixel来表示这张图片,也就是一个很长很长的vector,而output则是由图像类别组成的vector,假设你有1000个类别,那output就有1000个dimension
但是,我们现在会遇到的问题是这样子:实际上,在train neural network的时候,我们会有一种期待说,在这个network structure里面的每一个neuron,都应该代表了一个最基本的classifier;事实上,在文献上,根据训练的结果,也有很多人得到这样的结论,举例来说,下图中:
- 第一个layer的neuron,它就是最简单的classifier,它做的事情就是detect有没有绿色出现、有没有黄色出现、有没有斜的条纹出现等等
- 那第二个layer,它做的事情是detect更复杂的东西,根据第一个layer的output,它如果看到直线横线,就是窗框的一部分;如果看到棕色的直条纹就是木纹;看到斜条纹加灰色的,这个有可能是很多东西,比如说,轮胎的一部分等等
- 再根据第二个hidden layer的output,第三个hidden layer会做更复杂的事情,比如它可以知道说,当某一个neuron看到蜂巢,它就会被activate;当某一个neuron看到车子,它就会被activate;当某一个neuron看到人的上半身,它就会被activate等等
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/neuron-classifier.png" width="60%;"></center>
那现在的问题是这样子:**当我们直接用一般的fully connected的feedforward network来做图像处理的时候,往往会需要太多的参数**
举例来说,假设这是一张100\*100的彩色图片,它的分辨率才100\*100,那这已经是很小张的image了,然后你需要把它拉成一个vector,总共有100\*100\*3个pixel(如果是彩色的图的话,每个pixel其实需要3个value,即RGB值来描述它的),把这些加起来input vectot就已经有三万维了;如果input vector是三万维,又假设hidden layer有1000个neuron,那仅仅是第一层hidden layer的参数就已经有30000\*1000个了,这样就太多了
所以,**CNN做的事情其实是,来简化这个neural network的架构,我们根据自己的知识和对图像处理的理解,一开始就把某些实际上用不到的参数给过滤掉**,我们一开始就想一些办法,不要用fully connected network,而是用比较少的参数,来做图像处理这件事情,所以CNN其实是比一般的DNN还要更简单的
虽然CNN看起来,它的运作比较复杂,但事实上,它的模型比DNN还要更简单,我们就是用prior knowledge,去把原来fully connected的layer里面的一些参数拿掉,就变成CNN
##### Three Property for CNN theory base
为什么我们有可能把一些参数拿掉?为什么我们有可能只用比较少的参数就可以来做图像处理这件事情?下面列出三个对影像处理的观察:(**这也是CNN架构提出的基础所在!!!**)
###### Some patterns are much smaller than the whole image
在影像处理里面,如果在network的第一层hidden layer里,那些neuron要做的事情是侦测有没有一种东西、一种pattern(图案样式)出现,那大部分的pattern其实是比整张image要小的,所以对一个neuron来说,想要侦测有没有某一个pattern出现,它其实并不需要看整张image,只需要看这张image的一小部分,就可以决定这件事情了
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pattern.png" width="60%;"></center>
举例来说,假设现在我们有一张鸟的图片,那第一层hidden layer的某一个neuron的工作是,检测有没有鸟嘴的存在(你可能还有一些neuron侦测有没有鸟嘴的存在、有一些neuron侦测有没有爪子的存在、有一些neuron侦测有没有翅膀的存在、有没有尾巴的存在,之后合起来,就可以侦测,图片中有没有一只鸟),那它其实并不需要看整张图,因为,其实我们只要给neuron看这个小的红色杠杠里面的区域,它其实就可以知道说,这是不是一个鸟嘴,对人来说也是一样,只要看这个小的区域你就会知道说这是鸟嘴,所以,**每一个neuron其实只要连接到一个小块的区域就好,它不需要连接到整张完整的图,因此也对应着更少的参数**
###### The same patterns appear in different regions
同样的pattern,可能会出现在image的不同部分,但是它们有同样的形状、代表的是同样的含义,因此它们也可以用同样的neuron、同样的参数,被同一个detector检测出来
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pattern-region.png" width="60%;"></center>
举例来说,上图中分别有一个处于左上角的鸟嘴和一个处于中央的鸟嘴,但你并不需要训练两个不同的detector去专门侦测左上角有没有鸟嘴和中央有没有鸟嘴这两件事情,这样做太冗余了,我们要cost down(降低成本),我们并不需要有两个neuron、两组不同的参数来做duplicate(重复一样)的事情,所以**我们可以要求这些功能几乎一致的neuron共用一组参数,它们share同一组参数就可以帮助减少总参数的量**
###### Subsampling the pixels will not change the object
我们可以对一张image做subsampling(二次抽样),假如你把它奇数行、偶数列的pixel拿掉,image就可以变成原来的十分之一大小,而且并不会影响人对这张image的理解,对你来说,下面两张大小不一的image看起来不会有什么太大的区别,你都可以识别里面有什么物件,因此subsampling对图像辨识来说,可能是没有太大的影响的
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/subsamp.png" width="60%;"></center>
所以,**我们可以利用subsampling这个概念把image变小,从而减少需要用到的参数量**
#### The whole CNN structure
整个CNN的架构是这样的:
首先,input一张image以后,它会先通过Convolution的layer,接下来做Max Pooling这件事,然后再去做Convolution,再做Maxi Pooling...,这个process可以反复进行多次(重复次数需要事先决定),这就是network的架构,就好像network有几层一样,你要做几次convolution,做几次Max Pooling,在定这个network的架构时就要事先决定好
当你做完先前决定的convolution和max pooling的次数后,你要做的事情是Flatten,做完flatten以后,你就把Flatten output丢到一般的Fully connected network里面去,最终得到影像辨识的结果
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/whole-cnn.png" width="60%;"></center>
我们基于之前提到的三个对影像处理的观察,设计了CNN这样的架构,第一个是要侦测一个pattern,你不需要看整张image,只要看image的一个小部分;第二个是同样的pattern会出现在一张图片的不同区域;第三个是我们可以对整张image做subsampling
那**前面这两个property,是用convolution的layer来处理的;最后这个property,是用max pooling来处理的**
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/property.png" width="60%;"></center>
#### Convolution
假设现在我们network的input是一张6\*6的image,图像是黑白的,因此每个pixel只需要用一个value来表示,而在convolution layer里面,有一堆Filter,这边的每一个Filter,其实就等同于是Fully connected layer里的一个neuron
##### Property 1
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/filter.png" width="60%;"></center>
每一个Filter其实就是一个matrix,这个matrix里面每一个element的值,就跟那些neuron的weight和bias一样,是network的parameter,它们具体的值都是通过Training data学出来的,而不是人去设计的
所以,每个Filter里面的值是什么,要做什么事情,都是自动学习出来的,上图中每一个filter是3\*3的size,意味着它就是在侦测一个3\*3的pattern,**当它侦测的时候,并不会去看整张image,它只看一个3\*3范围内的pixel,就可以判断某一个pattern有没有出现,这就考虑了property 1**
##### Property 2
这个filter是从image的左上角开始,做一个slide window,每次向右挪动一定的距离,这个距离就叫做stride,由你自己设定,每次filter停下的时候就跟image中对应的3\*3的matrix做一个内积(相同位置的值相乘并累计求和),这里假设stride=1,那么我们的filter每次移动一格,当它碰到image最右边的时候,就从下一行的最左边开始重复进行上述操作,经过一整个convolution的process,最终得到下图所示的红色的4\*4 matrix
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/filter1.png" width="60%;"></center>
观察上图中的Filter1,它斜对角的地方是1,1,1,所以它的工作就是detect有没有连续的从左上角到右下角的1,1,1出现在这个image里面,检测到的结果已在上图中用蓝线标识出来,此时filter得到的卷积结果的左上和左下得到了最大的值,这就代表说,该filter所要侦测的pattern出现在image的左上角和左下角
**同一个pattern出现在image左上角的位置和左下角的位置,并不需要用到不同的filter,我们用filter1就可以侦测出来,这就考虑了property 2**
##### Feature Map
在一个convolution的layer里面,它会有一打filter,不一样的filter会有不一样的参数,但是这些filter做卷积的过程都是一模一样的,你把filter2跟image做完convolution以后,你就会得到另外一个蓝色的4\*4 matrix,那这个蓝色的4\*4 matrix跟之前红色的4\*4matrix合起来,就叫做**Feature Map(特征映射)**,有多少个filter,对应就有多少个映射后的image
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/filter2.png" width="60%;"></center>
CNN对**不同scale的相同pattern的处理**上存在一定的困难,由于现在每一个filter size都是一样的,这意味着,如果你今天有同一个pattern,它有不同的size,有大的鸟嘴,也有小的鸟嘴,CNN并不能够自动处理这个问题;DeepMind曾经发过一篇paper,上面提到了当你input一张image的时候,它在CNN前面,再接另外一个network,这个network做的事情是,它会output一些scalar,告诉你说,它要把这个image的里面的哪些位置做旋转、缩放,然后,再丢到CNN里面,这样你其实会得到比较好的performance
#### Colorful image
刚才举的例子是黑白的image,所以你input的是一个matrix,如果今天是彩色的image会怎么样呢?我们知道彩色的image就是由RGB组成的,所以一个彩色的image,它就是好几个matrix叠在一起,是一个立方体,如果我今天要处理彩色的image,要怎么做呢?
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/rgb.png" width="60%;"></center>
这个时候你的filter就不再是一个matrix了,它也会是一个立方体,如果你今天是RGB这三个颜色来表示一个pixel的话,那你的input就是3\*6\*6,你的filter就是3\*3\*3,你的filter的高就是3,你在做convolution的时候,就是把这个filter的9个值跟这个image里面的9个值做内积,可以想象成filter的每一层都分别跟image的三层做内积,得到的也是一个三层的output,每一个filter同时就考虑了不同颜色所代表的channel
#### Convolution V.s. Fully connected
##### filter是特殊的”neuron“
接下来要讲的是,convolution跟fully connected有什么关系,你可能觉得说,它是一个很特别的operation,感觉跟neural network没半毛钱关系,其实,它就是一个neural network
convolution这件事情,其实就是fully connected的layer把一些weight拿掉而已,下图中绿色方框标识出的feature map的output,其实就是hidden layer的neuron的output
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/convolution-fully.png" width="60%;"></center>
接下来我们来解释这件事情:
如下图所示,我们在做convolution的时候,把filter放在image的左上角,然后再去做inner product,得到一个值3;这件事情等同于,我们现在把这个image的6\*6的matrix拉直变成右边这个用于input的vector,然后,你有一个红色的neuron,这些input经过这个neuron之后,得到的output是3
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/filter-neuron1.png" width="60%;"></center>
##### 每个“neuron”只检测image的部分区域
那这个neuron的output怎么来的呢?这个neuron实际上就是由filter转化而来的,我们把filter放在image的左上角,此时filter考虑的就是和它重合的9个pixel,假设你把这一个6\*6的image的36个pixel拉成直的vector作为input,那这9个pixel分别就对应着右侧编号1,2,3的pixel,编号7,8,9的pixel跟编号13,14,15的pixel
如果我们说这个filter和image matrix做inner product以后得到的output 3,就是input vector经过某个neuron得到的output 3的话,这就代表说存在这样一个neuron,这个neuron带weight的连线,就只连接到编号为1,2,3,7,8,9,13,14,15的这9个pixel而已,而这个neuron和这9个pixel连线上所标注的的weight就是filter matrix里面的这9个数值
作为对比,Fully connected的neuron是必须连接到所有36个input上的,但是,我们现在只用连接9个input,因为我们知道要detect一个pattern,不需要看整张image,看9个input pixel就够了,所以当我们这么做的时候,就用了比较少的参数
##### “neuron”之间共享参数
当我们把filter做stride = 1的移动的时候,会发生什么事呢?此时我们通过filter和image matrix的内积得到另外一个output值-1,我们假设这个-1是另外一个neuron的output,那这个neuron会连接到哪些input呢?下图中这个框起来的地方正好就对应到pixel 2,3,4,pixel 8,9,10跟pixel 14,15,16
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/share-weight.png" width="60%;"></center>
你会发现output为3和-1的这两个neuron,它们分别去检测在image的两个不同位置上是否存在某个pattern,因此在Fully connected layer里它们做的是两件不同的事情,每一个neuron应该有自己独立的weight
但是,当我们做这个convolution的时候,首先我们把每一个neuron前面连接的weight减少了,然后我们强迫某些neuron(比如上图中output为3和-1的两个neuron),它们一定要共享一组weight,虽然这两个neuron连接到的pixel对象各不相同,但它们用的weight都必须是一样的,等于filter里面的元素值,这件事情就叫做weight share,当我们做这件事情的时候,用的参数,又会比原来更少
##### 总结
因此我们可以这样想,有这样一些特殊的neuron,它们只连接着9条带weight的线(9=3\*3对应着filter的元素个数,这些weight也就是filter内部的元素值,上图中圆圈的颜色与连线的颜色一一对应)
当filter在image matrix上移动做convolution的时候,每次移动做的事情实际上是去检测这个地方有没有某一种pattern,对于Fully connected layer来说,它是对整张image做detection的,因此每次去检测image上不同地方有没有pattern其实是不同的事情,所以这些neuron都必须连接到整张image的所有pixel上,并且不同neuron的连线上的weight都是相互独立的
==**那对于convolution layer来说,首先它是对image的一部分做detection的,因此它的neuron只需要连接到image的部分pixel上,对应连线所需要的weight参数就会减少;其次由于是用同一个filter去检测不同位置的pattern,所以这对convolution layer来说,其实是同一件事情,因此不同的neuron,虽然连接到的pixel对象各不相同,但是在“做同一件事情”的前提下,也就是用同一个filter的前提下,这些neuron所使用的weight参数都是相同的,通过这样一张weight share的方式,再次减少network所需要用到的weight参数**==
CNN的本质,就是减少参数的过程
##### 补充
看到这里你可能会问,这样的network该怎么搭建,又该怎么去train呢?
首先,第一件事情就是这都是用toolkit做的,所以你大概不会自己去写;如果你要自己写的话,它其实就是跟原来的Backpropagation用一模一样的做法,只是有一些weight就永远是0,你就不去train它,它就永远是0
然后,怎么让某些neuron的weight值永远都是一样呢?你就用一般的Backpropagation的方法,对每个weight都去算出gradient,再把本来要tight在一起、要share weight的那些weight的gradient平均,然后,让他们update同样值就ok了
#### Max Pooling
##### Operation of max pooling
相较于convolution,max pooling是比较简单的,它就是做subsampling,根据filter 1,我们得到一个4\*4的matrix,根据filter 2,你得到另外一个4\*4的matrix,接下来,我们要做什么事呢?
我们把output四个分为一组,每一组里面通过选取平均值或最大值的方式,把原来4个value合成一个 value,这件事情相当于在image每相邻的四块区域内都挑出一块来检测,这种subsampling的方式就可以让你的image缩小!
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/max-pooling.png" width="60%;"></center>
讲到这里你可能会有一个问题,如果取Maximum放到network里面,不就没法微分了吗?max这个东西,感觉是没有办法对它微分的啊,其实是可以的,后面的章节会讲到Maxout network,会告诉你怎么用微分的方式来处理它
##### Convolution + Max Pooling
所以,结论是这样的:
做完一次convolution加一次max pooling,我们就把原来6\*6的image,变成了一个2\*2的image;至于这个2\*2的image,它每一个pixel的深度,也就是每一个pixel用几个value来表示,就取决于你有几个filter,如果你有50个filter,就是50维,像下图中是两个filter,对应的深度就是两维
所以,这是一个新的比较小的image,它表示的是不同区域上提取到的特征,实际上不同的filter检测的是该image同一区域上的不同特征属性,所以每一层channel(通道)代表的是一种属性,一块区域有几种不同的属性,就有几层不同的channel,对应的就会有几个不同的filter对其进行convolution操作
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/max-pool.png" width="60%;"></center>
这件事情可以repeat很多次,你可以把得到的这个比较小的image,再次进行convolution和max pooling的操作,得到一个更小的image,依次类推
有这样一个问题:假设我第一个convolution有25个filter,通过这些filter得到25个feature map,然后repeat的时候第二个convolution也有25个filter,那这样做完,我是不是会得到25^2个feature map?
其实不是这样的,你这边做完一次convolution,得到25个feature map之后再做一次convolution,还是会得到25个feature map,因为convolution在考虑input的时候,是会考虑深度的,它并不是每一个channel分开考虑,而是一次考虑所有的channel,所以,你convolution这边有多少个filter,再次output的时候就会有多少个channel
因此你这边有25个channel,经过含有25个filter的convolution之后output还会是25个channel,只是这边的每一个channel,它都是一个cubic(立方体),它的高有25个value那么高
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/channel.png" width="60%;"></center>
#### Flatten
做完convolution和max pooling之后,就是FLatten和Fully connected Feedforward network的部分
Flatten的意思是,把左边的feature map拉直,然后把它丢进一个Fully connected Feedforward network,然后就结束了,也就是说,我们之前通过CNN提取出了image的feature,它相较于原先一整个image的vetor,少了很大一部分内容,因此需要的参数也大幅度地减少了,但最终,也还是要丢到一个Fully connected的network中去做最后的分类工作
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/fatten.png" width="50%;"></center>
#### CNN in Keras
##### 内容简介
接下来就讲一下,如何用Keras来implement CNN,实际上在compile、training和fitting的部分,内容跟DNN是一模一样的,对CNN来说,唯一需要改变的是network structure,以及input的format
本来在DNN里,input是一个由image拉直展开而成的vector,但现在如果是CNN的话,它是会考虑input image的几何空间的,所以不能直接input一个vector,而是要input一个tensor给它(tensor就是高维的vector),这里你要给它一个三维的vector,一个image的长宽各是一维,如果它是彩色的话,RGB就是第三维,所以你要assign一个三维的matrix,这个高维的matrix就叫做tensor
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/cnn-keras1.png" width="60%;"></center>
那怎么implement一个convolution的layer呢?
~~~python
model2.add( Convolution2D(25,3,3, input_shape=(28,28,1)) )
~~~
还是用`model.add`增加CNN的layer,将原先的Dense改成Convolution2D,参数25代表你有25个filter,参数3,3代表你的filter都是3\*3的matrix,此外你还需要告诉model,你input的image的shape是什么样子的,假设我现在要做手写数字识别,input就是28\*28的image,又因为它的每一个pixel都只有单一颜色,因此`input_shape`的值就是(28,28,1),如果是RGB的话,1就要改成3
然后增加一层Max Pooling的layer
~~~python
model2.add( MaxPooling2D(2,2) )
~~~
这里参数(2,2)指的是,我们把通过convolution得到的feature map,按照2\*2的方式分割成一个个区域,每次选取最大的那个值,并将这些值组成一个新的比较小的image,作为subsampling的结果
##### 过程描述
- 假设我们input是一个1\*28\*28的image
- 通过25个filter的convolution layer以后你得到的output,会有25个channel,又因为filter的size是3\*3,因此如果不考虑image边缘处的处理的话,得到的channel会是26\*26的,因此通过第一个convolution得到25\*26\*26的cubic image(这里把这张image想象成长宽为26,高为25的cubic立方体)
- 接下来就是做Max pooling,把2\*2的pixel分为一组,然后从里面选一个最大的组成新的image,大小为25\*13\*13(cubic长宽各被砍掉一半)
- 再做一次convolution,假设这次选择50个filter,每个filter size是3\*3的话,output的channel就变成有50个,那13\*13的image,通过3\*3的filter,就会变成11\*11,因此通过第二个convolution得到50\*11\*11的image(得到一个新的长宽为11,高为50的cubic)
- 再做一次Max Pooling,变成50\*50\*5
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/cnn-keras3.png" width="60%;"></center>
在第一个convolution里面,每一个filter都有9个参数,它就是一个3\*3的matrix;但是在第二个convolution layer里面,虽然每一个filter都是3\*3,但它其实不是3\*3个参数,因为它的input是一个25\*13\*13的cubic,这个cubic的channel有25个,所以要用同样高度的cubic filter对它进行卷积,于是我们的filter实际上是一个25\*3\*3的cubic,所以这边每个filter共有225个参数
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/cnn-keras2.png" width="60%;"></center>
通过两次convolution和max pooling的组合,最终的image变成了50\*5\*5的size,然后使用Flatten将这个image拉直,变成一个1250维的vector,再把它丢到一个Fully Connected Feedforward network里面,network structure就搭建完成了
##### 一个重要的问题
看到这里,你可能会有一个疑惑,第二次convolution的input是25\*13\*13的cubic,用50个3\*3的filter卷积后,得到的输出时应该是50个cubic,且每个cubic的尺寸为25\*11\*11,那么max pooling把长宽各砍掉一半后就是50层25\*5\*5的cubic,那flatten后不应该就是50\*25\*5\*5吗?
其实不是这样的,在第二次做convolution的时候,我们是用25\*3\*3的cubic filter对25\*13\*13的cubic input进行卷积操作的,filter的每一层和input cubic中对应的每一层(也就是每一个channel),它们进行内积后,还要把cubic的25个channel的内积值进行求和,作为这个“neuron”的output,它是一个scalar,这个cubic filter对整个cubic input做完一遍卷积操作后,得到的是一层scalar,然后有50个cubic filter,对应着50层scalar,因此最终得到的output是一个50\*11\*11的cubic!
这里的关键是filter和image都是cubic,每个cubic filter有25层高,它和同样有25层高的cubic image做卷积,并不是单单把每个cubic对应的channel进行内积,还会把这些内积求和!!!最终变为1层,因此==**两个矩阵或者tensor做了卷积后,不管之前的维数如何,都会变为一个scalor!**==,故如果有50个Filter,无论input是什么样子的,最终的output还会是50层
#### Appendix:CNN in Keras2.0
这里还是举**手写数字识别**的例子,将单纯使用DNN和加上CNN的情况作为对比
##### code
~~~python
import numpy as np
from keras.models import Sequential
from keras.layers import Convolution2D, MaxPooling2D, Flatten, Conv2D
from keras.layers.core import Dense, Dropout, Activation
from keras.optimizers import SGD, Adam
from keras.utils import np_utils
from keras.datasets import mnist
# categorical_crossentropy
def load_mnist_data(number):
# the data, shuffled and split between train and test sets
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train = x_train[0:number]
y_train = y_train[0:number]
x_train = x_train.reshape(number, 784)
x_test = x_test.reshape(10000, 784)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
# convert class vectors to binary class matrices
y_train = np_utils.to_categorical(y_train, 10)
y_test = np_utils.to_categorical(y_test, 10)
x_train = x_train / 255
x_test = x_test / 255
return (x_train, y_train), (x_test, y_test)
if __name__ == '__main__':
(x_train, y_train), (x_test, y_test) = load_mnist_data(10000)
# do DNN
model = Sequential()
model.add(Dense(input_dim=28 * 28, units=500, activation='relu'))
model.add(Dense(units=500, activation='relu'))
model.add(Dense(units=500, activation='relu'))
model.add(Dense(units=10, activation='softmax'))
model.summary()
model.compile(loss='categorical_crossentropy',
optimizer='adam', metrics=['accuracy'])
model.fit(x_train, y_train, batch_size=100, epochs=20)
result_train = model.evaluate(x_train, y_train)
print('\nTrain Acc:\n', result_train[1])
result_test = model.evaluate(x_test, y_test)
print('\nTest Acc:\n', result_test[1])
# do CNN
x_train = x_train.reshape(x_train.shape[0], 1, 28, 28)
x_test = x_test.reshape(x_test.shape[0], 1, 28, 28)
model2 = Sequential()
model2.add(Conv2D(25, (3, 3), input_shape=(
1, 28, 28), data_format='channels_first'))
model2.add(MaxPooling2D((2, 2)))
model2.add(Conv2D(50, (3, 3)))
model2.add(MaxPooling2D((2, 2)))
model2.add(Flatten())
model2.add(Dense(units=100, activation='relu'))
model2.add(Dense(units=10, activation='softmax'))
model2.summary()
model2.compile(loss='categorical_crossentropy',
optimizer='adam', metrics=['accuracy'])
model2.fit(x_train, y_train, batch_size=100, epochs=20)
result_train = model2.evaluate(x_train, y_train)
print('\nTrain CNN Acc:\n', result_train[1])
result_test = model2.evaluate(x_test, y_test)
print('\nTest CNN Acc:\n', result_test[1])
~~~
##### result
###### DNN
~~~python
Using TensorFlow backend.
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense_1 (Dense) (None, 500) 392500
_________________________________________________________________
dense_2 (Dense) (None, 500) 250500
_________________________________________________________________
dense_3 (Dense) (None, 500) 250500
_________________________________________________________________
dense_4 (Dense) (None, 10) 5010
=================================================================
Total params: 898,510
Trainable params: 898,510
Non-trainable params: 0
_________________________________________________________________
Epoch 1/20
10000/10000 [==============================] - 2s 207us/step - loss: 0.4727 - acc: 0.8643
Epoch 2/20
10000/10000 [==============================] - 1s 149us/step - loss: 0.1613 - acc: 0.9521
Epoch 3/20
10000/10000 [==============================] - 2s 159us/step - loss: 0.0916 - acc: 0.9726
Epoch 4/20
10000/10000 [==============================] - 2s 173us/step - loss: 0.0680 - acc: 0.9769
Epoch 5/20
10000/10000 [==============================] - 2s 166us/step - loss: 0.0437 - acc: 0.9850
Epoch 6/20
10000/10000 [==============================] - 2s 166us/step - loss: 0.0274 - acc: 0.9921
Epoch 7/20
10000/10000 [==============================] - 2s 168us/step - loss: 0.0265 - acc: 0.9892
Epoch 8/20
10000/10000 [==============================] - 2s 161us/step - loss: 0.0240 - acc: 0.9916
Epoch 9/20
10000/10000 [==============================] - 2s 169us/step - loss: 0.0149 - acc: 0.9950
Epoch 10/20
10000/10000 [==============================] - 2s 155us/step - loss: 0.0258 - acc: 0.9933
Epoch 11/20
10000/10000 [==============================] - 2s 168us/step - loss: 0.0206 - acc: 0.9934
Epoch 12/20
10000/10000 [==============================] - 2s 161us/step - loss: 0.0132 - acc: 0.9955
Epoch 13/20
10000/10000 [==============================] - 2s 168us/step - loss: 0.0113 - acc: 0.9964
Epoch 14/20
10000/10000 [==============================] - 2s 169us/step - loss: 0.0027 - acc: 0.9991
Epoch 15/20
10000/10000 [==============================] - 2s 157us/step - loss: 6.6533e-04 - acc: 0.9999
Epoch 16/20
10000/10000 [==============================] - 1s 150us/step - loss: 1.1253e-04 - acc: 1.0000
Epoch 17/20
10000/10000 [==============================] - 2s 152us/step - loss: 8.3190e-05 - acc: 1.0000
Epoch 18/20
10000/10000 [==============================] - 2s 174us/step - loss: 6.7850e-05 - acc: 1.0000
Epoch 19/20
10000/10000 [==============================] - 2s 173us/step - loss: 5.6810e-05 - acc: 1.0000
Epoch 20/20
10000/10000 [==============================] - 2s 172us/step - loss: 4.8757e-05 - acc: 1.0000
10000/10000 [==============================] - 1s 97us/step
Train Acc: 1.0
10000/10000 [==============================] - 1s 77us/step
Test Acc: 0.9661
~~~
###### CNN
~~~python
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_1 (Conv2D) (None, 25, 26, 26) 250
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 12, 13, 26) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 10, 11, 50) 11750
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 5, 5, 50) 0
_________________________________________________________________
flatten_1 (Flatten) (None, 1250) 0
_________________________________________________________________
dense_5 (Dense) (None, 100) 125100
_________________________________________________________________
dense_6 (Dense) (None, 10) 1010
=================================================================
Total params: 138,110
Trainable params: 138,110
Non-trainable params: 0
_________________________________________________________________
Epoch 1/20
10000/10000 [==============================] - 8s 785us/step - loss: 0.6778 - acc: 0.8113
Epoch 2/20
10000/10000 [==============================] - 7s 734us/step - loss: 0.2302 - acc: 0.9349
Epoch 3/20
10000/10000 [==============================] - 8s 765us/step - loss: 0.1562 - acc: 0.9532
Epoch 4/20
10000/10000 [==============================] - 8s 760us/step - loss: 0.1094 - acc: 0.9680
Epoch 5/20
10000/10000 [==============================] - 8s 843us/step - loss: 0.0809 - acc: 0.9763
Epoch 6/20
10000/10000 [==============================] - 7s 748us/step - loss: 0.0664 - acc: 0.9810
Epoch 7/20
10000/10000 [==============================] - 8s 764us/step - loss: 0.0529 - acc: 0.9832
Epoch 8/20
10000/10000 [==============================] - 7s 747us/step - loss: 0.0370 - acc: 0.9904
Epoch 9/20
10000/10000 [==============================] - 7s 687us/step - loss: 0.0302 - acc: 0.9919
Epoch 10/20
10000/10000 [==============================] - 7s 690us/step - loss: 0.0224 - acc: 0.9940
Epoch 11/20
10000/10000 [==============================] - 7s 698us/step - loss: 0.0177 - acc: 0.9959
Epoch 12/20
10000/10000 [==============================] - 7s 690us/step - loss: 0.0154 - acc: 0.9965
Epoch 13/20
10000/10000 [==============================] - 7s 692us/step - loss: 0.0126 - acc: 0.9962
Epoch 14/20
10000/10000 [==============================] - 7s 689us/step - loss: 0.0130 - acc: 0.9966
Epoch 15/20
10000/10000 [==============================] - 7s 691us/step - loss: 0.0092 - acc: 0.9977
Epoch 16/20
10000/10000 [==============================] - 7s 691us/step - loss: 0.0067 - acc: 0.9986
Epoch 17/20
10000/10000 [==============================] - 7s 687us/step - loss: 0.0069 - acc: 0.9985
Epoch 18/20
10000/10000 [==============================] - 7s 691us/step - loss: 0.0040 - acc: 0.9995
Epoch 19/20
10000/10000 [==============================] - 7s 745us/step - loss: 0.0020 - acc: 1.0000
Epoch 20/20
10000/10000 [==============================] - 8s 782us/step - loss: 0.0014 - acc: 1.0000
10000/10000 [==============================] - 7s 657us/step
Train CNN Acc: 1.0
10000/10000 [==============================] - 5s 526us/step
Test CNN Acc: 0.98
~~~

View File

@@ -0,0 +1,233 @@
# Convolutional Neural Network part2
> 人们常常会说,deep learning就是一个黑盒子,你learn完以后根本就不知道它得到了什么,所以会有很多人不喜欢这种方法,这篇文章就讲述了三个问题:What does CNN do?Why CNN?How to design CNN?
#### What does CNN learn?
##### what is intelligent
如果今天有一个方法,它可以让你轻易地理解为什么这个方法会下这样的判断和决策的话,那其实你会觉得它不够intelligent;它必须要是你无法理解的东西,这样它才够intelligent,至少你会感觉它很intelligent
所以,大家常说deep learning就是一个黑盒子,你learn出来以后,根本就不知道为什么是这样子,于是你会感觉它很intelligent,但是其实还是有很多方法可以分析的,今天我们就来示范一下怎么分析CNN,看一下它到底学到了什么
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/cnn-keras3.png" width="60%;"></center>
要分析第一个convolution的filter是比较容易的,因为第一个convolution layer里面,每一个filter就是一个3\*3的matrix,它对应到3\*3范围内的9个pixel,所以你只要看这个filter的值,就可以知道它在detect什么东西,因此第一层的filter是很容易理解的
但是你比较没有办法想像它在做什么事情的,是第二层的filter,它们是50个同样为3\*3的filter,但是这些filter的input并不是pixel,而是做完convolution再做Max pooling的结果,因此filter考虑的范围并不是3\*3=9个pixel,而是一个长宽为3\*3,高为25的cubic,filter实际在image上看到的范围是远大于9个pixel的,所以你就算把它的weight拿出来,也不知道它在做什么
##### what does filter do
那我们怎么来分析一个filter它做的事情是什么呢?你可以这样做:
我们知道在第二个convolution layer里面的50个filter,每一个filter的output就是一个11\*11的matrix,假设我们现在把第k个filter的output拿出来,如下图所示,这个matrix里的每一个element,我们叫它$a^k_{ij}$,上标k表示这是第k个filter,下标ij表示它在这个matrix里的第i个row,第j个column
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/kth-filter.png" width="60%;" /></center>
接下来我们define一个$a^k$叫做**Degree of the activation of the k-th filter**,这个值表示现在的第k个filter,它有多被activate,有多被“启动”,直观来讲就是描述现在input的东西跟第k个filter有多接近,它对filter的激活程度有多少
第k个filter被启动的degree $a^k$就定义成,它与input进行卷积所输出的output里所有element的summation,以上图为例,就是这11*11的output matrix里所有元素之和,用公式描述如下:
$$
a^k=\sum\limits^{11}_{i=1}\sum\limits^{11}_{j=1} a^k_{ij}
$$
也就是说,我们input一张image,然后把这个filter和image进行卷积所output的11\*11个值全部加起来,当作现在这个filter被activate的程度
接下来我们要做的事情是这样子,我们想要知道第k个filter的作用是什么,那我们就要找一张image,这张image可以让第k个filter被activate的程度最大;于是我们现在要解的问题是,找一个image x,它可以让我们定义的activation的degree $a^k$最大,即:
$$
x^*=\arg \max\limits_x a^k
$$
之前我们求minimize用的是gradient descent,那现在我们求Maximum用gradient ascent(梯度上升法)就可以做到这件事了
仔细一想这个方法还是颇为神妙的,因为我们现在是把input x作为要找的参数,对它去用gradient descent或ascent进行update,原来在train CNN的时候,input是固定的,model的参数是要用gradient descent去找出来的;但是现在这个立场是反过来的,在这个task里面model的参数是固定的,我们要用gradient ascent去update这个x,让它可以使degree of activation最大
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/gradient-ascent.png" width="60%;" /></center>
上图就是得到的结果,50个filter理论上可以分别找50张image使对应的activation最大,这里仅挑选了其中的12张image作为展示,这些image有一个共同的特征,它们里面都是一些**反复出现的某种texture(纹路)**,比如说第三张image上布满了小小的斜条纹,这意味着第三个filter的工作就是detect图上有没有斜条纹,要知道现在每个filter检测的都只是图上一个小小的范围而已,所以图中一旦出现一个小小的斜条纹,这个filter就会被activate,相应的output也会比较大,所以如果整张image上布满这种斜条纹的话,这个时候它会最兴奋,filter的activation程度是最大的,相应的output值也会达到最大
因此每个filter的工作就是去detect某一种pattern,detect某一种线条,上图所示的filter所detect的就是不同角度的线条,所以今天input有不同线条的话,某一个filter会去找到让它兴奋度最高的匹配对象,这个时候它的output就是最大的
##### what does neuron do
我们做完convolution和max pooling之后,会将结果用Flatten展开,然后丢到Fully connected的neural network里面去,之前已经搞清楚了filter是做什么的,那我们也想要知道在这个neural network里的每一个neuron是做什么的,所以就对刚才的做法如法炮制
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/neuron-do.png" width="60%;" /></center>
我们定义第j个neuron的output就是$a_j$,接下来就用gradient ascent的方法去找一张image x,把它丢到neural network里面就可以让$a_j$的值被maximize,即:
$$
x^*=\arg \max\limits_x a^j
$$
找到的结果如上图所示,同理这里仅取出其中的9张image作为展示,你会发现这9张图跟之前filter所观察到的情形是很不一样的,刚才我们观察到的是类似纹路的东西,那是因为每个filter考虑的只是图上一部分的vision,所以它detect的是一种texture;但是在做完Flatten以后,每一个neuron不再是只看整张图的一小部分,它现在的工作是看整张图,所以对每一个neuron来说,让它最兴奋的、activation最大的image,不再是texture,而是一个完整的图形
##### what about output
接下来我们考虑的是CNN的output,由于是手写数字识别的demo,因此这里的output就是10维,我们把某一维拿出来,然后同样去找一张image x,使这个维度的output值最大,即
$$
x^*=\arg \max_x y^i
$$
你可以想象说,既然现在每一个output的每一个dimension就对应到一个数字,那如果我们去找一张image x,它可以让对应到数字1的那个output layer的neuron的output值最大,那这张image显然应该看起来会像是数字1,你甚至可以期待,搞不好用这个方法就可以让machine自动画出数字
但实际上,我们得到的结果是这样子,如下图所示
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/cnn-output.png" width="60%;" /></center>
上面的每一张图分别对应着数字0-8,你会发现,可以让数字1对应neuron的output值最大的image其实长得一点也不像1,就像是电视机坏掉的样子,为了验证程序有没有bug,这里又做了一个实验,把上述得到的image真的作为testing data丢到CNN里面,结果classify的结果确实还是认为这些image就对应着数字0-8
所以今天这个neural network,它所学到的东西跟我们人类一般的想象认知是不一样的
那我们有没有办法,让上面这个图看起来更像数字呢?想法是这样的,我们知道一张图是不是一个数字,它会有一些基本的假设,比如这些image,你不知道它是什么数字,你也会认为它显然就不是一个digit,因为人类手写出来的东西就不是长这个样子的,所以我们要对这个x做一些regularization,我们要对找出来的x做一些constraint(限制约束),我们应该告诉machine说,虽然有一些x可以让你的y很大,但是它们不是数字
那我们应该加上什么样的constraint呢?最简单的想法是说,画图的时候,白色代表的是有墨水、有笔画的地方,而对于一个digit来说,整张image上涂白的区域是有限的,像上面这些整张图都是白白的,它一定不会是数字
假设image里的每一个pixel都用$x_{ij}$表示,我们把所有pixel值取绝对值并求和,也就是$\sum\limits_{i,j}|x_{ij}|$,这一项其实就是之前提到过的L1的regularization,再用$y^i$减去这一项,得到
$$
x^*=\arg \max\limits_x (y^i-\sum\limits_{i,j} |x_{ij}|)
$$
这次我们希望再找一个input x,它可以让$y^i$最大的同时,也要让$|x_ij|$的summation越小越好,也就是说我们希望找出来的image,大部分的地方是没有涂颜色的,只有少数数字笔画在的地方才有颜色出现
加上这个constraint以后,得到的结果会像下图右侧所示一样,已经隐约有些可以看出来是数字的形状了
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/L1.png" width="60%;" /></center>
如果再加上一些额外的constraint,比如你希望相邻的pixel是同样的颜色等等,你应该可以得到更好的结果
#### Deep Dream
其实,这就是Deep Dream的精神,Deep Dream是说,如果你给machine一张image,它会在这个image里面加上它看到的东西
怎么做这件事情呢?你就找一张image丢到CNN里面去,然后你把某一个convolution layer里面的filter或是fully connected layer里的某一个hidden layer的output拿出来,它其实是一个vector;接下来把本来是positive的dimension值调大,negative的dimension值调小,也就是让正的更正,负的更负,然后把它作为新的image的目标
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/deep-dream.png" width="60%;" /></center>
这里就是把3.9、2.3的值调大,-1.5的值调小,总体来说就是使它们的绝对值变大,然后用gradient descent的方法找一张image x,让它通过这个hidden layer后的output就是你调整后的target,这么做的目的就是,**让CNN夸大化它看到的东西**——make CNN exaggerates what is sees
也就是说,如果某个filter有被activate,那你让它被activate的更剧烈,CNN可能本来看到了某一样东西,那现在你就让它看起来更像原来看到的东西,这就是所谓的**夸大化**
如果你把上面这张image拿去做Deep Dream的话,你看到的结果就会像下面这个样子
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/deep-dream2.png" width="60%;" /></center>
就好像背后有很多念兽,要凝才看得到,比如像上图右侧那一只熊,它原来是一个石头,对机器来说,它看这张图的时候,本来就觉得这个石头有点像熊,所以你就更强化这件事,让它看起来真的就变成了一只熊,这个就是Deep Dream
#### Deep Style
Deep Dream还有一个进阶的版本,就叫做Deep Style,如果今天你input一张image,Deep Style做的事情就是让machine去修改这张图,让它有另外一张图的风格,如下所示
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/deep-style.png" width="60%;" /></center>
实际上机器做出来的效果惊人的好,具体的做法参考reference:[A Neural Algorithm of Artistic Style](https://arxiv.org/abs/1508.06576)
这里仅讲述Deep Style的大致思路,你把原来的image丢给CNN,得到CNN filter的output,代表这样image里面有什么样的content,然后你把呐喊这张图也丢到CNN里面得到filter的output,注意,我们并不在于一个filter output的value到底是什么,一个单独的数字并不能代表任何的问题,我们真正在意的是,filter和filter的output之间的correlation,这个correlation代表了一张image的style
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/deep-style2.png" width="60%;" /></center>
接下来你就再用一个CNN去找一张image,**这张image的content像左边的图片**,比如这张image的filter output的value像左边的图片;同时让**这张image的style像右边的图片**,所谓的style像右边的图片是说,这张image output的filter之间的correlation像右边这张图片
最终你用gradient descent找到一张image,同时可以maximize左边的content和右边的style,它的样子就像上图左下角所示
#### More Application——Playing Go
##### What does CNN do in Playing Go
CNN可以被运用到不同的应用上,不只是影像处理,比如出名的alphaGo
想要让machine来下围棋,不见得要用CNN,其实一般typical的neural network也可以帮我们做到这件事情
你只要learn一个network,也就是找一个function,它的input是棋盘当前局势,output是你下一步根据这个棋盘的盘势而应该落子的位置,这样其实就可以让machine学会下围棋了,所以用fully connected的feedforward network也可以做到让machine下围棋这件事情
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/play-go.png" width="60%;" /></center>
也就是说,你只要告诉它input是一个19\*19的vector,vector的每一个dimension对应到棋盘上的某一个位置,如果那一个位置有一个黑子的话,就是1,如果有一个白子的话,就是-1,反之呢,就是0,所以如果你把棋盘描述成一个19\*19的vector,丢到一个fully connected的feedforward network里,output也是19\*19个dimension ,每一个dimension对应到棋盘上的一个位置,那machine就可以学会下围棋了
但实际上如果我们采用CNN的话,会得到更好的performance,我们之前举的例子都是把CNN用在图像上面,也就是input是一个matrix,而棋盘其实可以很自然地表示成一个19\*19的matrix,那对CNN来说,就是直接把它当成一个image来看待,然后再output下一步要落子的位置,具体的training process是这样的:
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/go-process.png" width="60%;" /></center>
你就搜集很多棋谱,比如说上图这个是进藤光和社青春的棋谱,初手下在5之五,次手下在天元,然后再下在5之五,接下来你就告诉machine说,看到落子在5之五,CNN的output就是天元的地方是1,其他的output是0;看到5之五和天元都有子,那你的output就是5之五的地方是1,其他都是0
上面是supervised的部分,那其实呢AlphaGo还有reinforcement learning的部分,这个后面的章节会讲到
##### Why CNN for Playing Go
自从AlphaGo用了CNN以后,大家都觉得好像CNN应该很厉害,所以有时候如果你没有用CNN来处理问题,人家就会来问你;比如你去面试的时候,你的硕士论文里面没有用CNN来处理问题,口试的人可能不知道CNN是什么 ,但是他就会问你说为什么不用CNN呢,CNN不是比较强吗?这个时候如果你真的明白了为什么要用CNN,什么时候才要用CNN这个问题,你就可以直接给他怼回去
那什么时候我们可以用CNN呢?你要有image该有的那些特性,也就是上一篇文章开头所说的,根据观察到的三个property,我们才设计出了CNN这样的network架构:
- **Some patterns are much smaller than the whole image**
- **The same patterns appear in different regions**
- **Subsampling the pixels will not change the object**
CNN能够应用在Alpha-Go上,是因为围棋有一些特性和图像处理是很相似的
在property 1,有一些pattern是比整张image要小得多,在围棋上,可能也有同样的现象,比如下图中一个白子被3个黑子围住,这个叫做吃,如果下一个黑子落在白子下面,就可以把白子提走;只有另一个白子接在下面,它才不会被提走
那现在你只需要看这个小小的范围,就可以侦测这个白子是不是属于被叫吃的状态,你不需要看整个棋盘,才知道这件事情,所以这件事情跟image有着同样的性质;在AlphaGo里面,它第一个layer其实就是用5\*5的filter,显然做这个设计的人,觉得围棋上最基本的pattern可能都是在5\*5的范围内就可以被侦测出来
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/why-cnn-go.png" width="60%;" /></center>
在property 2,同样的pattern可能会出现在不同的region,在围棋上也可能有这个现象,像这个叫吃的pattern,它可以出现在棋盘的左上角,也可以出现在右下角,它们都是叫吃,都代表了同样的意义,所以你可以用同一个detector,来处理这些在不同位置的同样的pattern
所以对围棋来说呢,它在第一个observation和第二个observation是有这个image的特性的,但是,让我们没有办法想通的地方,就是第三点
##### Max Pooling for Alpha Go?——read alpha-go paper
我们可以对一个image做subsampling,你拿掉奇数行、偶数列的pixel,把image变成原来的1/4的大小也不会影响你看这张图的样子,基于这个观察才有了Max pooling这个layer;但是,对围棋来说,它可以做这件事情吗?比如说,你对一个棋盘丢掉奇数行和偶数列,那它还和原来是同一个函式吗?显然不是的
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/go-property3.png" width="60%;" /></center>
如何解释在棋盘上使用Max Pooling这件事情呢?有一些人觉得说,因为AlphaGo使用了CNN,它里面有可能用了Max pooling这样的构架,所以,或许这是它的一个弱点,你要是针对这个弱点攻击它,也许就可以击败它
AlphaGo的paper内容不多,只有6页左右,它只说使用了CNN,却没有在正文里面仔细地描述它的CNN构架,但是在这篇paper长长附录里,其实是有描述neural network structure的,如上图所示
它是这样说的,input是一个19\*19\*48的image,其中19\*19是棋盘的格局,对Alpha来说,每一个位置都用48个value来描述,这是因为加上了domain knowledge,它不只是描述某位置有没有白子或黑子,它还会观察这个位置是不是处于叫吃的状态等等
先用一个hidden layer对image做zero padding,也就是把原来19\*19的image外围补0,让它变成一张23\*23的image,然后使用k个5\*5的filter对该image做convolution,stride设为1,activation function用的是ReLU,得到的output是21\*21的image;接下来使用k个3\*3的filter,stride设为1,activation function还是使用ReLU,...
你会发现这个AlphaGo的network structure一直在用convolution,其实**根本就没有使用Max Pooling**,原因并不是疏失了什么之类的,而是根据围棋的特性,我们本来就不需要在围棋的CNN里面,用Max pooling这样的构架
举这个例子是为了告诉大家:
==**neural network架构的设计,是应用之道,存乎一心**==
#### More Application——Speech、Text
##### Speech
CNN也可以用在很多其他的task里面,比如语音处理上,我们可以把一段声音表示成spectrogram,spectrogram的横轴是时间,纵轴则是这一段时间里声音的频率
下图中是一段“你好”的音频,偏红色代表这段时间里该频率的energy是比较大的,也就对应着“你”和“好”这两个字,也就是说spectrogram用颜色来描述某一个时刻不同频率的能量
我们也可以让机器把这个spectrogram就当作一张image,然后用CNN来判断说,input的这张image对应着什么样的声音信号,那通常用来判断结果的单位,比如phoneme,就是类似音标这样的单位
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/cnn-speech.png" width="60%;" /></center>
这边比较神奇的地方就是,当我们把一段spectrogram当作image丢到CNN里面的时候,在语音上,我们通常只考虑在frequency(频率)方向上移动的filter,我们的filter就像上图这样,是长方形的,它的宽就跟image的宽是一样的,并且**filter只在Frequency即纵坐标的方向上移动,而不在时间的序列上移动**
这是因为在语音里面,CNN的output后面都还会再接别的东西,比如接LSTM之类,它们都已经有考虑typical的information,所以你在CNN里面再考虑一次时间的information其实没有什么特别的帮助,但是为什么在频率上 的filter有帮助呢?
我们用CNN的目的是为了用同一个filter把相同的pattern给detect出来,在声音讯号上,虽然男生和女生说同样的话看起来这个spectrogram是非常不一样的,但实际上他们的不同只是表现在一个频率的shift而已(整体在频率上的位移),男生说的“你好”跟女生说的“你好”,它们的pattern其实是一样的,比如pattern是spectrogram变化的情形,男生女生的声音的变化情况可能是一样的,它们的差别可能只是所在的频率范围不同而已,所以filter在frequency的direction上移动是有效的
所以,这又是另外一个例子,当你把CNN用在一个Application的时候呢,你永远要想一想这个Application的特性是什么,根据这个特性你再去design network的structure,才会真正在理解的基础上去解决问题
##### Text
CNN也可以用在文字处理上,假设你的input是一个word sequence,你要做的事情是让machine侦测这个word sequence代表的意思是positive的还是negative的
首先你把这个word sequence里面的每一个word都用一个vector来表示,vector代表的这个word本身的semantic (语义),那如果两个word本身含义越接近的话,它们的vector在高维的空间上就越接近,这个东西就叫做word embedding
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/cnn-text.png" width="60%;" /></center>
把一个sentence里面所有word的vector排在一起,它就变成了一张image,你把CNN套用到这个image上,那filter的样子就是上图蓝色的matrix,它的高和image的高是一样的,然后把filter沿着句子里词汇的顺序来移动,每个filter移动完成之后都会得到一个由内积结果组成的vector,不同的filter就会得到不同的vector,接下来做Max pooling,然后把Max pooling的结果丢到fully connected layer里面,你就会得到最后的output
与语音处理不同的是,**在文字处理上,filter只在时间的序列(按照word的顺序)上移动,而不在这个embedding的dimension上移动**;因为在word embedding里面,不同dimension是independent的,它们是相互独立的,不会出现有两个相同的pattern的情况,所以在这个方向上面移动filter,是没有意义的
所以这又是另外一个例子,虽然大家觉得CNN很powerful,你可以用在各个不同的地方,但是当你应用到一个新的task的时候,你要想一想这个新的task在设计CNN的构架的时候,到底该怎么做
#### conclusion
本文的重点在于CNN的theory base,也就是What is CNN?What does CNN do?Why CNN?总结起来就是三个property、两个架构和一个理念,这也是使用CNN的条件基础:
##### 三个property
- **Some patterns are much smaller than the whole image** ——property 1
- **The same patterns appear in different regions** ——property 2
- **Subsampling the pixels will not change the object** ——property 3
##### 两个架构
convolution架构:针对property 1和property 2
max pooling架构:针对property 3
##### 一个理念
针对不同的application要设计符合它特性的network structure,而不是生硬套用,这就是CNN架构的设计理念:
==**应用之道,存乎一心**==

View File

@@ -0,0 +1,581 @@
# Tips for Deep Learning
> 本文会顺带解决CNN部分的两个问题:
> 1、max pooling架构中用到的max无法微分,那在gradient descent的时候该如何处理?
> 2、L1 的Regression到底是什么东西
>
> 本文的主要思路:针对training set和testing set上的performance分别提出针对性的解决方法
> 1、在training set上准确率不高:
> new activation function:ReLU、Maxout
> adaptive learning rate:Adagrad、RMSProp、Momentum、Adam
> 2、在testing set上准确率不高:Early Stopping、Regularization or Dropout
### Recipe of Deep Learning
#### three step of deep learning
Recipe,配方、秘诀,这里指的是做deep learning的流程应该是什么样子
我们都已经知道了deep learning的三个步骤
- define the function set(network structure)
- goodness of function(loss function -- cross entropy)
- pick the best function(gradient descent -- optimization)
做完这些事情以后,你会得到一个更好的neural network,那接下来你要做什么事情呢?
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/recipe-dl.png" width="60%;" /></center>
#### Good Results on Training Data?
你要做的第一件事是,**提高model在training set上的正确率**
先检查training set的performance其实是deep learning一个非常unique的地方,如果今天你用的是k-nearest neighbor或decision tree这类非deep learning的方法,做完以后你其实会不太想检查training set的结果,因为在training set上的performance正确率就是100,没有什么好检查的
有人说deep learning的model里这么多参数,感觉一脸很容易overfitting的样子,但实际上这个deep learning的方法,它才不容易overfitting,我们说的**overfitting就是在training set上performance很好,但在testing set上performance没有那么好**;只有像k nearest neighbor,decision tree这类方法,它们在training set上正确率都是100,这才是非常容易overfitting的,而对deep learning来说,overfitting往往不会是你遇到的第一个问题
因为你在training的时候,deep learning并不是像k nearest neighbor这种方法一样,一训练就可以得到非常好的正确率,它有可能在training set上根本没有办法给你一个好的正确率,所以,这个时候你要回头去检查在前面的step里面要做什么样的修改,好让你在training set上可以得到比较高的正确率
#### Good Results on Testing Data?
接下来你要做的事是,**提高model在testing set上的正确率**
假设现在你已经在training set上得到好的performance了,那接下来就把model apply到testing set上,我们最后真正关心的,是testing set上的performance,假如得到的结果不好,这个情况下发生的才是Overfitting,也就是在training set上得到好的结果,却在testing set上得到不好的结果
那你要回过头去做一些事情,试着解决overfitting,但有时候你加了新的technique,想要overcome overfitting这个problem的时候,其实反而会让training set上的结果变坏;所以你在做完这一步的修改以后,要先回头去检查新的model在training set上的结果,如果这个结果变坏的话,你就要从头对network training的process做一些调整,那如果你同时在training set还有testing set上都得到好结果的话,你就成功了,最后就可以把你的系统真正用在application上面了
#### Do not always blame overfitting
不要看到所有不好的performance就归责于overfitting
先看右边testing data的图,横坐标是model做gradient descent所update的次数,纵坐标则是error rate(越低说明model表现得越好),黄线表示的是20层的neural network,红色表示56层的neural network
你会发现,这个56层network的error rate比较高,它的performance比较差,而20层network的performance则是比较好的,有些人看到这个图,就会马上得到一个结论:56层的network参数太多了,56层果然没有必要,这个是overfitting。但是,真的是这样子吗?
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/blame-over.png" width="60%;" /></center>
你在说结果是overfitting之前,有检查过training set上的performance吗?对neural network来说,在training set上得到的结果很可能会像左边training error的图,也就是说,20层的network本来就要比56层的network表现得更好,所以testing set得到的结果并不能说明56层的case就是发生了overfitting
在做neural network training的时候,有太多太多的问题可以让你的training set表现的不好,比如说我们有local minimum的问题,有saddle point的问题,有plateau的问题...所以这个56层的neural network,有可能在train的时候就卡在了一个local minimum的地方,于是得到了一个差的参数,但这并不是overfitting,而是在training的时候就没有train好
有人认为这个问题叫做underfitting,但我的理解上,**underfitting**的本意应该是指这个model的complexity不足,这个model的参数不够多,所以它的能力不足以解出这个问题;但这个56层的network,它的参数是比20层的network要来得多的,所以它明明有能力比20层的network要做的更好,却没有得到理想的结果,这种情况不应该被称为underfitting,其实就只是没有train好而已
#### conclusion
当你在deep learning的文献上看到某种方法的时候,永远要想一下,这个方法是要解决什么样的问题,因为在deep learning里面,有两个问题:
- 在training set上的performance不够好
- 在testing set上的performance不够好
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/different-methods.png" width="60%;" /></center>
当只有一个方法propose(提出)的时候,它往往只针对这两个问题的其中一个来做处理,举例来说,deep learning有一个很潮的方法叫做dropout,那很多人就会说,哦,这么潮的方法,所以今天只要看到performance不好,我就去用dropout;但是,其实只有在testing的结果不好的时候,才可以去apply dropout,如果你今天的问题只是training的结果不好,那你去apply dropout,只会越train越差而已
所以,你**必须要先想清楚现在的问题到底是什么,然后再根据这个问题去找针对性的方法**,而不是病急乱投医,甚至是盲目诊断
下面我们分别从Training data和Testing data两个问题出发,来讲述一些针对性优化的方法
### Good Results on Training Data?
这一部分主要讲述如何在Training data上得到更好的performance,分为两个模块,New activation function和Adaptive Learning Rate
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/different-method.png" width="60%;" /></center>
#### New activation function
这个部分主要讲述的是关于Recipe of Deep Learning中New activation function的一些理论
##### activation function
如果你今天的training结果不好,很有可能是因为你的network架构设计得不好。举例来说,可能你用的activation function是对training比较不利的,那你就尝试着换一些新的activation function,也许可以带来比较好的结果
在1980年代,比较常用的activation function是sigmoid function,如果现在我们使用sigmoid function,你会发现deeper不一定imply better,下图是在MNIST手写数字识别上的结果,当layer越来越多的时候,accuracy一开始持平,后来就掉下去了,在layer是9层、10层的时候,整个结果就崩溃了;但注意!9层、10层的情况并不能被认为是因为参数太多而导致overfitting,实际上这张图就只是training set的结果,你都不知道testing的情况,又哪来的overfitting之说呢?
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/deep-not-ok.png" width="60%;" /></center>
##### Vanishing Gradient Problem
上面这个问题的原因不是overfitting,而是Vanishing Gradient(梯度消失),解释如下:
当你把network叠得很深的时候,在靠近input的地方,这些参数的gradient(即对最后loss function的微分)是比较小的;而在比较靠近output的地方,它对loss的微分值会是比较大的
因此当你设定同样learning rate的时候,靠近input的地方,它参数的update是很慢的;而靠近output的地方,它参数的update是比较快的
所以在靠近input的地方,参数几乎还是random的时候,output就已经根据这些random的结果找到了一个local minima,然后就converge(收敛)了
这个时候你会发现,参数的loss下降的速度变得很慢,你就会觉得gradient已经接近于0了,于是把程序停掉了,由于这个converge,是几乎base on random的参数,所以model的参数并没有被训练充分,那在training data上得到的结果肯定是很差的
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/vanishing.png" width="60%;" /></center>
为什么会有这个现象发生呢?如果你自己把Backpropagation的式子写出来的话,就可以很轻易地发现用sigmoid function会导致这件事情的发生;但是,我们今天不看Backpropagation的式子,其实从直觉上来想你也可以了解这件事情发生的原因
某一个参数$w$对total cost $l$的偏微分,即gradient $\frac{\partial l}{\partial w}$,它直觉的意思是说,当我今天把这个参数做小小的变化的时候,它对这个cost的影响有多大;那我们就把第一个layer里的某一个参数$w$加上$\Delta w$,看看对network的output和target之间的loss有什么样的影响
$\Delta w$通过sigmoid function之后,得到output是会变小的,改变某一个参数的weight,会对某个neuron的output值产生影响,但是这个影响是会随着层数的递增而衰减的,sigmoid function的形状如下所示,它会把负无穷大到正无穷大之间的值都硬压到0~1之间,把较大的input压缩成较小的output
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/sigmoid-less.png" width="35%;" /></center>
因此即使$\Delta w$值很大,但每经过一个sigmoid function就会被缩小一次,所以network越深,$\Delta w$被衰减的次数就越多,直到最后,它对output的影响就是比较小的,相应的也导致input对loss的影响会比较小,于是靠近input的那些weight对loss的gradient $\frac{\partial l}{\partial w}$远小于靠近output的gradient
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/vanish.png" width="60%;" /></center>
那怎么解决这个问题呢?比较早年的做法是去train RBM,它的精神就是,先把第一个layer train好,再去train第二个,然后再第三个...所以最后你在做Backpropagation的时候,尽管第一个layer几乎没有被train到,但一开始在做pre-train的时候就已经把它给train好了,这样RBM就可以在一定程度上解决问题
但其实改一下activation function可能就可以handle这个问题了
##### ReLU
###### introduction
现在比较常用的activation function叫做Rectified Linear Unit(整流线性单元函数,又称修正线性单元),它的缩写是ReLU,该函数形状如下图所示,z为input,a为output,如果input>0则output = input,如果input<0则output = 0
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/ReLU1.png" width="60%;" /></center>
选择ReLU的理由如下:
- 跟sigmoid function比起来,ReLU的运算快很多
- ReLU的想法结合了生物上的观察( Pengel的paper )
- 无穷多bias不同的sigmoid function叠加的结果会变成ReLU
- ReLU可以处理Vanishing gradient的问题( the most important thing )
###### handle Vanishing gradient problem
下图是ReLU的neural network,以ReLU作为activation function的neuron,它的output要么等于0,要么等于input
当output=input的时候,这个activation function就是linear的;而output=0的neuron对整个network是没有任何作用的,因此可以把它们从network中拿掉
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/relu1.png" width="60%;" /></center>
拿掉所有output为0的neuron后如下图所示,此时整个network就变成了一个瘦长的**linear** network,linear的好处是,output=input,不会像sigmoid function一样使input产生的影响逐层递减
Q:这里就会有一个问题,我们之所以使用deep learning,就是因为想要一个non-linear、比较复杂的function,而使用ReLU不就会让它变成一个linear function吗?这样得到的function不是会变得很弱吗?
A:其实,使用ReLU之后的network整体来说还是non-linear的,如果你对input做小小的改变,不改变neuron的operation region的话,那network就是一个linear function;但是,如果你对input做比较大的改变,导致neuron的operation region被改变的话,比如从output=0转变到了output=input,network整体上就变成了non-linear function
注:这里的region是指input z<0和input z>0的两个范围
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/relu2.png" width="60%;" /></center>
Q:还有另外一个问题,我们对loss function做gradient descent,要求neural network是可以做微分的,但ReLU是一个分段函数,它是不能微分的(至少在z=0这个点是不可微的),那该怎么办呢?
A:在实际操作上,当region的范围处于z>0时,微分值gradient就是1;当region的范围处于z<0时,微分值gradient就是0;当z为0时,就不要管它,相当于把它从network里面拿掉
###### ReLU-variant
其实ReLU还存在一定的问题,比如当input<0的时候,output=0,此时微分值gradient也为0,你就没有办法去update参数了,所以我们应该让input<0的时候,微分后还能有一点点的值,比如令$a=0.01z$,这个东西就叫做**Leaky ReLU**
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/relu-variant.png" width="60%;" /></center>
既然a可以等于0.01z,那这个z的系数可不可以是0.07、0.08之类呢?所以就有人提出了**Parametric ReLU**,也就是令$a=\alpha \cdot z$,其中$\alpha$并不是固定的值,而是network的一个参数,它可以通过training data学出来,甚至每个neuron都可以有不同的$\alpha$值
这个时候又有人想,为什么一定要是ReLU这样子呢,activation function可不可以有别的样子呢?所以后来有了一个更进阶的想法,叫做**Maxout network**
##### Maxout
###### introduction
Maxout的想法是,让network自动去学习它的activation function,那Maxout network就可以自动学出ReLU,也可以学出其他的activation function,这一切都是由training data来决定的
假设现在有input $x_1,x_2$,它们乘上几组不同的weight分别得到5,7,-1,1,这些值本来是不同neuron的input,它们要通过activation function变为neuron的output;但在Maxout network里,我们事先决定好将某几个“neuron”的input分为一个group,比如5,7分为一个group,然后在这个group里选取一个最大值7作为output
这个过程就好像在一个layer上做Max Pooling一样,它和原来的network不同之处在于,它把原来几个“neuron”的input按一定规则组成了一个group,然后并没有使它们通过activation function,而是选取其中的最大值当做这几个“neuron”的output
当然,实际上原来的”neuron“早就已经不存在了,这几个被合并的“neuron”应当被看做是一个新的neuron,这个新的neuron的input是原来几个“neuron”的input组成的vector,output则取input的最大值,而并非由activation function产生
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/maxout1.png" width="60%;" /></center>
在实际操作上,几个element被分为一个group这件事情是由你自己决定的,它就是network structure里一个需要被调的参数,不一定要跟上图一样两个分为一组
###### Maxout -> RELU
Maxout是如何模仿出ReLU这个activation function的呢?
下图左上角是一个ReLU的neuron,它的input x会乘上neuron的weight w,再加上bias b,然后通过activation function-ReLU,得到output a
- neuron的input为$z=wx+b$,为下图左下角紫线
- neuron的output为$a=z\ (z>0);\ a=0\ (z<0)$,为下图左下角绿线
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/maxout2.png" width="60%;" /></center>
如果我们使用的是上图右上角所示的Maxout network,假设$z_1$的参数w和b与ReLU的参数一致,而$z_2$的参数w和b全部设为0,然后做Max Pooling,选取$z_1,z_2$较大值作为a
- neuron的input为$\begin{bmatrix}z_1 \ z_2 \end{bmatrix}$
- $z_1=wx+b$,为上图右下角紫线
- $z_2=0$,为上图右下角红线
- neuron的output为$\max{\begin{bmatrix}z_1 \ z_2 \end{bmatrix}}$,为上图右下角绿线
你会发现,此时ReLU和Maxout所得到的output是一模一样的,它们是相同的activation function
###### Maxout -> More than ReLU
除了ReLU,Maxout还可以实现更多不同的activation function
比如$z_2$的参数w和b不是0,而是$w',b'$,此时
- neuron的input为$\begin{bmatrix}z_1 \ z_2 \end{bmatrix}$
- $z_1=wx+b$,为下图右下角紫线
- $z_2=w'x+b'$,为下图右下角红线
- neuron的output为$\max{\begin{bmatrix}z_1 \ z_2 \end{bmatrix}}$,为下图右下角绿线
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/maxout3.png" width="60%;" /></center>
这个时候你得到的activation function的形状(绿线形状),是由network的参数$w,b,w',b'$决定的,因此它是一个**Learnable Activation Function**,具体的形状可以根据training data去generate出来
###### property
Maxout可以实现任何piecewise linear convex activation function(分段线性凸激活函数),其中这个activation function被分为多少段,取决于你把多少个element z放到一个group里,下图分别是2个element一组和3个element一组的activation function的不同形状
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/maxout4.png" width="60%;" /></center>
###### How to train Maxout
接下来我们要面对的是,怎么去train一个Maxout network,如何解决Max不能微分的问题
假设在下面的Maxout network中,红框圈起来的部分为每个neuron的output
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/maxout-train.png" width="60%;" /></center>
其实Max operation就是linear的operation,只是它仅接在前面这个group里的某一个element上,因此我们可以把那些并没有被Max连接到的element通通拿掉,从而得到一个比较细长的linear network
实际上我们真正训练的并不是一个含有max函数的network,而是一个化简后如下图所示的linear network;当我们还没有真正开始训练模型的时候,此时这个network含有max函数无法微分,但是只要真的丢进去了一笔data,network就会马上根据这笔data确定具体的形状,此时max函数的问题已经被实际数据给解决了,所以我们完全可以根据这笔training data使用Backpropagation的方法去训练被network留下来的参数
所以我们担心的max函数无法微分,它只是理论上的问题;**在具体的实践上,我们完全可以先根据data把max函数转化为某个具体的函数,再对这个转化后的thiner linear network进行微分**
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/maxout-train2.png" width="60%;" /></center>
这个时候你也许会有一个问题,如果按照上面的做法,那岂不是只会train留在network里面的那些参数,剩下的参数该怎么办?那些被拿掉的直线(weight)岂不是永远也train不到了吗?
其实这也只是个理论上的问题,在实际操作上,我们之前已经提到过,每个linear network的structure都是由input的那一笔data来决定的,当你input不同data的时候,得到的network structure是不同的,留在network里面的参数也是不同的,**由于我们有很多很多笔training data,所以network的structure在训练中不断地变换,实际上最后每一个weight参数都会被train到**
所以,我们回到Max Pooling的问题上来,由于Max Pooling跟Maxout是一模一样的operation,既然如何训练Maxout的问题可以被解决,那训练Max Pooling又有什么困难呢?
**Max Pooling有关max函数的微分问题采用跟Maxout一样的方案即可解决**,至此我们已经解决了CNN部分的第一个问题
#### Adaptive learning rate
这个部分主要讲述的是关于Recipe of Deep Learning中Adaptive learning rate的一些理论
##### Review - Adagrad
我们之前已经了解过Adagrad的做法,让每一个parameter都要有不同的learning rate
Adagrad的精神是,假设我们考虑两个参数$w_1,w_2$,如果在$w_1$这个方向上,平常的gradient都比较小,那它是比较平坦的,于是就给它比较大的learning rate;反过来说,在$w_2$这个方向上,平常gradient都比较大,那它是比较陡峭的,于是给它比较小的learning rate
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/review-adagrad.png" width="60%;" /></center>
但我们实际面对的问题,很有可能远比Adagrad所能解决的问题要来的复杂,我们之前做Linear Regression的时候,我们做optimization的对象,也就是loss function,它是convex的形状;但实际上我们在做deep learning的时候,这个loss function可以是任何形状
##### RMSProp
###### learning rate
loss function可以是任何形状,对convex loss function来说,在每个方向上它会一直保持平坦或陡峭的状态,所以你只需要针对平坦的情况设置较大的learning rate,对陡峭的情况设置较小的learning rate即可
但是在下图所示的情况中,即使是在同一个方向上(如w1方向),loss function也有可能一会儿平坦一会儿陡峭,所以你要随时根据gradient的大小来快速地调整learning rate
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/rmsprop1.png" width="60%;" /></center>
所以真正要处理deep learning的问题,用Adagrad可能是不够的,你需要更dynamic的调整learning rate的方法,所以产生了Adagrad的进阶版——**RMSProp**
RMSprop还是一个蛮神奇的方法,因为它并不是在paper里提出来的,而是Hinton在mooc的course里面提出来的一个方法,所以需要cite(引用)的时候,要去cite Hinton的课程链接
###### how to do RMSProp
RMSProp的做法如下:
我们的learning rate依旧设置为一个固定的值 $\eta$ 除掉一个变化的值 $\sigma$,这个$\sigma$等于上一个$\sigma$和当前梯度$g$的加权方均根(特别的是,在第一个时间点,$\sigma^0$就是第一个算出来的gradient值$g^0$),即:
$$
w^{t+1}=w^t-\frac{\eta}{\sigma^t}g^t \\
\sigma^t=\sqrt{\alpha(\sigma^{t-1})^2+(1-\alpha)(g^t)^2}
$$
这里的$\alpha$值是可以自由调整的,RMSProp跟Adagrad不同之处在于,Adagrad的分母是对过程中所有的gradient取平方和开根号,也就是说Adagrad考虑的是整个过程平均的gradient信息;而RMSProp虽然也是对所有的gradient进行平方和开根号,但是它**用一个$\alpha$来调整对不同gradient的使用程度**,比如你把α的值设的小一点,意思就是你更倾向于相信新的gradient所告诉你的error surface的平滑或陡峭程度,而比较无视于旧的gradient所提供给你的information
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/rmsprop2.png" width="60%;" /></center>
所以当你做RMSProp的时候,一样是在算gradient的root mean square,但是你可以给现在已经看到的gradient比较大的weight,给过去看到的gradient比较小的weight,来调整对gradient信息的使用程度
##### Momentum
###### optimization - local minima?
除了learning rate的问题以外,在做deep learning的时候,也会出现卡在local minimum、saddle point或是plateau的地方,很多人都会担心,deep learning这么复杂的model,可能非常容易就会被卡住了
但其实Yann LeCun在07年的时候,就提出了一个蛮特别的说法,他说你不要太担心local minima的问题,因为一旦出现local minima,它就必须在每一个dimension都是下图中这种山谷的低谷形状,假设山谷的低谷出现的概率为p,由于我们的network有非常非常多的参数,这里假设有1000个参数,每一个参数都要位于山谷的低谷之处,这件事发生的概率为$p^{1000}$,当你的network越复杂,参数越多,这件事发生的概率就越低
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/optimal1.png" width="60%;" /></center>
所以在一个很大的neural network里面,其实并没有那么多的local minima,搞不好它看起来其实是很平滑的,所以当你走到一个你觉得是local minima的地方被卡住了,那它八成就是global minima,或者是很接近global minima的地方
###### where is Momentum from
有一个heuristic(启发性)的方法可以稍微处理一下上面所说的“卡住”的问题,它的灵感来自于真实世界
假设在有一个球从左上角滚下来,它会滚到plateau的地方、local minima的地方,但是由于惯性它还会继续往前走一段路程,假设前面的坡没有很陡,这个球就很有可能翻过山坡,走到比local minima还要好的地方
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/physical.png" width="60%;" /></center>
所以我们要做的,就是把**惯性**塞到gradient descent里面,这件事情就叫做**Momentum**
###### how to do Momentum
当我们在gradient descent里加上Momentum的时候,每一次update的方向,不再只考虑gradient的方向,还要考虑上一次update的方向,那这里我们就用一个变量$v$去记录前一个时间点update的方向
随机选一个初始值$\theta^0$,初始化$v^0=0$,接下来计算$\theta^0$处的gradient,然后我们要移动的方向是由前一个时间点的移动方向$v^0$和gradient的反方向$\nabla L(\theta^0)$来决定的,即
$$
v^1=\lambda v^0-\eta \nabla L(\theta^0)
$$
注:这里的$\lambda$也是一个手动调整的参数,它表示惯性对前进方向的影响有多大
接下来我们第二个时间点要走的方向$v^2$,它是由第一个时间点移动的方向$v^1$和gradient的反方向$\nabla L(\theta^1)$共同决定的;$\lambda v$是图中的绿色虚线,它代表由于上一次的惯性想要继续走的方向;$\eta \nabla L(\theta)$是图中的红色虚线,它代表这次gradient告诉你所要移动的方向;它们的矢量和就是这一次真实移动的方向,为蓝色实线
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/momentum1.png" width="60%;" /></center>
gradient告诉我们走红色虚线的方向,惯性告诉我们走绿色虚线的方向,合起来就是走蓝色的方向
我们还可以用另一种方法来理解Momentum这件事,其实你在每一个时间点移动的步伐$v^i$,包括大小和方向,就是过去所有gradient的加权和
具体推导如下图所示,第一个时间点移动的步伐$v^1$是$\theta^0$处的gradient加权,第二个时间点移动的步伐$v^2$是$\theta^0$和$\theta^1$处的gradient加权和...以此类推;由于$\lambda$的值小于1,因此该加权意味着越是之前的gradient,它的权重就越小,也就是说,你更在意的是现在的gradient,但是过去的所有gradient也要对你现在update的方向有一定程度的影响力,这就是Momentum
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/momentum2.png" width="60%;" /></center>
如果你对数学公式不太喜欢的话,那我们就从直觉上来看一下加入Momentum之后是怎么运作的
在加入Momentum以后,每一次移动的方向,就是negative的gradient加上Momentum建议我们要走的方向,Momentum其实就是上一个时间点的movement
下图中,红色实线是gradient建议我们走的方向,直观上看就是根据坡度要走的方向;绿色虚线是Momentum建议我们走的方向,实际上就是上一次移动的方向;蓝色实线则是最终真正走的方向
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/momentum3.png" width="60%;" /></center>
如果我们今天走到local minimum的地方,此时gradient是0,红色箭头没有指向,它就会告诉你就停在这里吧,但是Momentum也就是绿色箭头,它指向右侧就是告诉你之前是要走向右边的,所以你仍然应该要继续往右走,所以最后你参数update的方向仍然会继续向右;你甚至可以期待Momentum比较强,惯性的力量可以支撑着你走出这个谷底,去到loss更低的地方
##### Adam
其实**RMSProp加上Momentum,就可以得到Adam**
根据下面的paper来快速描述一下Adam的algorithm:
- 先初始化$m_0=0$,$m_0$就是Momentum中,前一个时间点的movement
再初始化$v_0=0$,$v_0$就是RMSProp里计算gradient的root mean square的$\sigma$
最后初始化$t=0$,t用来表示时间点
- 先算出gradient $g_t$
$$
g_t=\nabla _{\theta}f_t(\theta_{t-1})
$$
- 再根据过去要走的方向$m_{t-1}$和gradient $g_t$,算出现在要走的方向 $m_t$——Momentum
$$
m_t=\beta_1 m_{t-1}+(1-\beta_1) g_t
$$
- 然后根据前一个时间点的$v_{t-1}$和gradient $g_t$的平方,算一下放在分母的$v_t$——RMSProp
$$
v_t=\beta_2 v_{t-1}+(1-\beta_2) g_t^2
$$
- 接下来做了一个原来RMSProp和Momentum里没有的东西,就是bias correction,它使$m_t$和$v_t$都除上一个值,这个值本来比较小,后来会越来越接近于1 (原理详见paper)
$$
\hat{m}_t=\frac{m_t}{1-\beta_1^t} \\ \hat{v}_t=\frac{v_t}{1-\beta_2^t}
$$
- 最后做update,把Momentum建议你的方向$\hat{m_t}$乘上learning rate $\alpha$,再除掉RMSProp normalize后建议的learning rate分母,然后得到update的方向
$$
\theta_t=\theta_{t-1}-\frac{\alpha \cdot \hat{m}_t}{\sqrt{\hat{v}_t}+\epsilon}
$$
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/adam.png" width="80%;" /></center>
### Good Results on Testing Data?
这一部分主要讲述如何在Testing data上得到更好的performance,分为三个模块,Early Stopping、Regularization和Dropout
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/result-test.png" width="60%;" /></center>
值得注意的是,Early Stopping和Regularization是很typical的做法,它们不是特别为deep learning所设计的;而Dropout是一个蛮有deep learning特色的做法
#### Early Stopping
假设你今天的learning rate调的比较好,那随着训练的进行,total loss通常会越来越小,但是Training set和Testing set的情况并不是完全一样的,很有可能当你在Training set上的loss逐渐减小的时候,在Testing set上的loss反而上升了
所以,理想上假如你知道testing data上的loss变化情况,你会在testing set的loss最小的时候停下来,而不是在training set的loss最小的时候停下来;但testing set实际上是未知的东西,所以我们需要用validation set来替代它去做这件事情
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/early-stop.png" width="60%;" /></center>
注:很多时候,我们所讲的“testing set”并不是指代那个未知的数据集,而是一些已知的被你拿来做测试之用的数据集,比如kaggle上的public set,或者是你自己切出来的validation set
#### Regularization
regularization就是在原来的loss function上额外增加几个term,比如我们要minimize的loss function原先应该是square error或cross entropy,那在做Regularization的时候,就在后面加一个Regularization的term
##### L2 regularization
regularization term可以是参数的L2 norm(L2正规化),所谓的L2 norm,就是把model参数集$\theta$里的每一个参数都取平方然后求和,这件事被称作L2 regularization,即
$$
L2 \ regularization:||\theta||_2=(w_1)^2+(w_2)^2+...
$$
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/regularization1.png" width="60%;" /></center>
通常我们在做regularization的时候,新加的term里是不会考虑bias这一项的,因为加regularization的目的是为了让我们的function更平滑,而bias通常是跟function的平滑程度没有关系的
你会发现我们新加的regularization term $\lambda \frac{1}{2}||\theta||_2$里有一个$\frac{1}{2}$,由于我们是要对loss function求微分的,而新加的regularization term是参数$w_i$的平方和,对平方求微分会多出来一个系数2,我们的$\frac{1}{2}$就是用来和这个2相消的
L2 regularization具体工作流程如下:
- 我们加上regularization term之后得到了一个新的loss function:$L'(\theta)=L(\theta)+\lambda \frac{1}{2}||\theta||_2$
- 将这个loss function对参数$w_i$求微分:$\frac{\partial L'}{\partial w_i}=\frac{\partial L}{\partial w_i}+\lambda w_i$
- 然后update参数$w_i$:$w_i^{t+1}=w_i^t-\eta \frac{\partial L'}{\partial w_i}=w_i^t-\eta(\frac{\partial L}{\partial w_i}+\lambda w_i^t)=(1-\eta \lambda)w_i^t-\eta \frac{\partial L}{\partial w_i}$
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/regularization2.png" width="60%;" /></center>
如果把这个推导出来的式子和原式作比较,你会发现参数$w_i$在每次update之前,都会乘上一个$(1-\eta \lambda)$,而$\eta$和$\lambda$通常会被设为一个很小的值,因此$(1-\eta \lambda)$通常是一个接近于1的值,比如0.99,;也就是说,regularization做的事情是,每次update参数$w_i$之前,不分青红皂白就先对原来的$w_i$乘个0.99,这意味着,随着update次数增加,参数$w_i$会越来越接近于0
Q:你可能会问,要是所有的参数都越来越靠近0,那最后岂不是$w_i$通通变成0,得到的network还有什么用?
A:其实不会出现最后所有参数都变为0的情况,因为通过微分得到的$\eta \frac{\partial L}{\partial w_i}$这一项是会和前面$(1-\eta \lambda)w_i^t$这一项最后取得平衡的
使用L2 regularization可以让weight每次都变得更小一点,这就叫做**Weight Decay**(权重衰减)
##### L1 regularization
除了L2 regularization中使用平方项作为new term之外,还可以使用L1 regularization,把平方项换成每一个参数的绝对值,即
$$
||\theta||_1=|w_1|+|w_2|+...
$$
Q:你的第一个问题可能会是,绝对值不能微分啊,该怎么处理呢?
A:实际上绝对值就是一个V字形的函数,在V的左边微分值是-1,在V的右边微分值是1,只有在0的地方是不能微分的,那真的走到0的时候就胡乱给它一个值,比如0,就ok了
如果w是正的,那微分出来就是+1,如果w是负的,那微分出来就是-1,所以这边写了一个w的sign function,它的意思是说,如果w是正数的话,这个function output就是+1,w是负数的话,这个function output就是-1
L1 regularization的工作流程如下:
- 我们加上regularization term之后得到了一个新的loss function:$L'(\theta)=L(\theta)+\lambda \frac{1}{2}||\theta||_1$
- 将这个loss function对参数$w_i$求微分:$\frac{\partial L'}{\partial w_i}=\frac{\partial L}{\partial w_i}+\lambda \ sgn(w_i)$
- 然后update参数$w_i$:$w_i^{t+1}=w_i^t-\eta \frac{\partial L'}{\partial w_i}=w_i^t-\eta(\frac{\partial L}{\partial w_i}+\lambda \ sgn(w_i^t))=w_i^t-\eta \frac{\partial L}{\partial w_i}-\eta \lambda \ sgn(w_i^t)$
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/regularization3.png" width="60%;" /></center>
这个式子告诉我们,每次update的时候,不管三七二十一都要减去一个$\eta \lambda \ sgn(w_i^t)$,如果w是正的,sgn是+1,就会变成减一个positive的值让你的参数变小;如果w是负的,sgn是-1,就会变成加一个值让你的参数变大;总之就是让它们的绝对值减小至接近于0
##### L1 V.s. L2
我们来对比一下L1和L2的update过程:
$$
L1: w_i^{t+1}=w_i^t-\eta \frac{\partial L}{\partial w_i}-\eta \lambda \ sgn(w_i^t)\\
L2: w_i^{t+1}=(1-\eta \lambda)w_i^t-\eta \frac{\partial L}{\partial w_i}
$$
L1和L2,虽然它们同样是让参数的绝对值变小,但它们做的事情其实略有不同:
- L1使参数绝对值变小的方式是每次update**减掉一个固定的值**
- L2使参数绝对值变小的方式是每次update**乘上一个小于1的固定值**
因此,当参数w的绝对值比较大的时候,L2会让w下降得更快,而L1每次update只让w减去一个固定的值,train完以后可能还会有很多比较大的参数;当参数w的绝对值比较小的时候,L2的下降速度就会变得很慢,train出来的参数平均都是比较小的,而L1每次下降一个固定的value,train出来的参数是比较sparse的,这些参数有很多是接近0的值,也会有很大的值
在之前所讲的CNN的task里,用L1做出来的效果是比较合适的,是比较sparse的
##### Weight Decay
之前提到了Weight Decay,那实际上我们在人脑里面也会做Weight Decay
下图分别描述了,刚出生的时候,婴儿的神经是比较稀疏的;6岁的时候,就会有很多很多的神经;但是到14岁的时候,神经间的连接又减少了,所以neural network也会跟我们人有一些很类似的事情,如果有一些weight你都没有去update它,那它每次都会越来越小,最后就接近0然后不见了
这跟人脑的运作,是有异曲同工之妙
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/regularization4.png" width="60%;" /></center>
#### some tips
ps:在deep learning里面,regularization虽然有帮助,但它的重要性往往没有SVM这类方法来得高,因为我们在做neural network的时候,通常都是从一个很小的、接近于0的值开始初始参数的,而做update的时候,通常都是让参数离0越来越远,但是regularization要达到的目的,就是希望我们的参数不要离0太远
如果你做的是Early Stopping,它会减少update的次数,其实也会避免你的参数离0太远,这跟regularization做的事情是很接近的
所以在neural network里面,regularization的作用并没有SVM来的重要,SVM其实是explicitly把regularization这件事情写在了它的objective function(目标函数)里面,SVM是要去解一个convex optimization problem,因此它解的时候不一定会有iteration的过程,它不会有Early Stopping这件事,而是一步就可以走到那个最好的结果了,所以你没有办法用Early Stopping防止它离目标太远,你必须要把regularization explicitly加到你的loss function里面去
#### Dropout
这里先讲dropout是怎么做的,然后再来解释为什么这样做
##### How to do Dropout
Dropout是怎么做的呢?
###### Training
在training的时候,每次update参数之前,我们对每一个neuron(也包括input layer的“neuron”)做sampling(抽样) ,每个neuron都有p%的几率会被丢掉,如果某个neuron被丢掉的话,跟它相连的weight也都要被丢掉
实际上就是每次update参数之前都通过抽样只保留network中的一部分neuron来做训练
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/dropout1.png" width="60%;" /></center>
做完sampling以后,network structure就会变得比较细长了,然后你再去train这个细长的network
注:每次update参数之前都要做一遍sampling,所以每次update参数的时候,拿来training的network structure都是不一样的;你可能会觉得这个方法跟前面提到的Maxout会有一点像,但实际上,Maxout是每一笔data对应的network structure不同,而Dropout是每一次update的network structure都是不同的(每一个minibatch对应着一次update,而一个minibatch里含有很多笔data)
当你在training的时候使用dropout,得到的performance其实是会变差的,因为某些neuron在training的时候莫名其妙就会消失不见,但这并不是问题,因为:
==**Dropout真正要做的事情,就是要让你在training set上的结果变差,但是在testing set上的结果是变好的**==
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/dropout2.png" width="60%;" /></center>
所以如果你今天遇到的问题是在training set上得到的performance不够好,你再加dropout,就只会越做越差;这告诉我们,不同的problem需要用不同的方法去解决,而不是胡乱使用,dropout就是针对testing set的方法,当然不能够拿来解决training set上的问题啦!
###### Testing
在使用dropout方法做testing的时候要注意两件事情:
- testing的时候不做dropout,所有的neuron都要被用到
- 假设在training的时候,dropout rate是p%,从training data中被learn出来的所有weight都要乘上(1-p%)才能被当做testing的weight使用
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/dropout3.png" width="60%;" /></center>
##### Why Dropout?
###### 为什么dropout会有用?
直接的想法是这样子:
在training的时候,会丢掉一些neuron,就好像是你要练轻功的时候,会在脚上绑一些重物;然后,你在实际战斗的时候,就是实际testing的时候,是没有dropout的,就相当于把重物拿下来,所以你就会变得很强
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/dropout4.png" width="60%;" /></center>
另一个直觉的理由是这样,neural network里面的每一个neuron就是一个学生,那大家被连接在一起就是大家听到说要组队做final project,那在一个团队里总是有人会拖后腿,就是他会dropout,所以假设你觉得自己的队友会dropout,这个时候你就会想要好好做,然后去carry这个队友,这就是training的过程
那实际在testing的时候,其实大家都有好好做,没有人需要被carry,由于每个人都比一般情况下更努力,所以得到的结果会是更好的,这也就是testing的时候不做dropout的原因
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/dropout5.png" width="60%;" /></center>
###### 为什么training和testing使用的weight是不一样的呢?
直觉的解释是这样的:
假设现在的dropout rate是50%,那在training的时候,你总是期望每次update之前会丢掉一半的neuron,就像下图左侧所示,在这种情况下你learn好了一组weight参数,然后拿去testing
但是在testing的时候是没有dropout的,所以如果testing使用的是和training同一组weight,那左侧得到的output z和右侧得到的output z‘,它们的值其实是会相差两倍的,即$z'≈2z$,这样会造成testing的结果与training的结果并不match,最终的performance反而会变差
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/dropout6.png" width="60%;" /></center>
那这个时候,你就需要把右侧testing中所有的weight乘上0.5,然后做normalization,这样z就会等于z',使得testing的结果与training的结果是比较match的
##### Dropout is a kind of ensemble
在文献上有很多不同的观点来解释为什么dropout会work,其中一种比较令人信服的解释是:**dropout是一种终极的ensemble的方法**
###### ensemble精神的解释
ensemble的方法在比赛的时候经常用得到,它的意思是说,我们有一个很大的training set,那你每次都只从这个training set里面sample一部分的data出来,像下图一样,抽取了set1,set2,set3,set4
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/dropout7.png" width="60%;" /></center>
我们之前在讲bias和variance的trade off的时候说过,打靶有两种情况:
- 一种是因为bias大而导致打不准(参数过少)
- 另一种是因为variance大而导致打不准(参数过多)
假设我们今天有一个很复杂的model,它往往是bias比较准,但variance很大的情况,如果你有很多个笨重复杂的model,虽然它们的variance都很大,但最后平均起来,结果往往就会很准
所以ensemble做的事情,就是利用这个特性,我们从原来的training data里面sample出很多subset,然后train很多个model,每一个model的structure甚至都可以不一样;在testing的时候,丢了一笔testing data进来,使它通过所有的model,得到一大堆的结果,然后把这些结果平均起来当做最后的output
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/dropout8.png" width="60%;" /></center>
如果你的model很复杂,这一招往往是很有用的,那著名的random forest(随机森林)也是实践这个精神的一个方法,也就是如果你用一个decision tree,它就会很弱,也很容易overfitting,而如果采用random forest,它就没有那么容易overfitting
###### 为什么dropout是一个终极的ensemble方法呢?
在training network的时候,每次拿一个minibatch出来就做一次update,而根据dropout的特性,每次update之前都要对所有的neuron进行sample,因此每一个minibatch所训练的network都是不同的
假设我们有M个neuron,每个neuron都有可能drop或不drop,所以总共可能的network数量有$2^M$个;所以当你在做dropout的时候,相当于是在用很多个minibatch分别去训练很多个network(一个minibatch一般设置为100笔data),由于update次数是有限的,所以做了几次update,就相当于train了几个不同的network,最多可以训练到$2^M$个network
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/dropout9.png" width="60%;" /></center>
每个network都只用一个minibatch的data来train,可能会让人感到不安,一个batch才100笔data,怎么train一个network呢?其实没有关系,因为这些**不同的network之间的参数是shared**,也就是说,虽然一个network只能用一个minibatch来train,但同一个weight可以在不同的network里被不同的minibatch train,所以同一个weight实际上是被所有没有丢掉它的network一起share的,它是拿所有这些network的minibatch合起来一起train的结果
###### 实际操作ensemble的做法
那按照ensemble这个方法的逻辑,在testing的时候,你把那train好的一大把network通通拿出来,然后把手上这一笔testing data丢到这把network里面去,每个network都给你吐出一个结果来,然后你把所有的结果平均起来 ,就是最后的output
但是在实际操作上,如下图左侧所示,这一把network实在太多了,你没有办法每一个network都丢一个input进去,再把它们的output平均起来,这样运算量太大了
所以dropout最神奇的地方是,当你并没有把这些network分开考虑,而是用一个完整的network,这个network的weight是用之前那一把network train出来的对应weight乘上(1-p%),然后再把手上这笔testing data丢进这个完整的network,得到的output跟network分开考虑的ensemble的output,是惊人的相近
也就是说下图左侧ensemble的做法和右侧dropout的做法,得到的结果是approximate(近似)的
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/dropout10.png" width="60%;" /></center>
###### 举例说明dropout和ensemble的关系
这里用一个例子来解释:
我们train一个下图右上角所示的简单的network,它只有一个neuron,activation function是linear的,并且不考虑bias,这个network经过dropout训练以后得到的参数分别为$w_1,w_2$,那给它input $x_1,x_2$,得到的output就是$z=w_1 x_1+w_2 x_2$
如果我们今天要做ensemble的话,theoretically就是像下图这么做,每一个neuron都有可能被drop或不drop,这里只有两个input的neuron,所以我们一共可以得到2^2=4种network
我们手上这笔testing data $x_1,x_2$丢到这四个network中,分别得到4个output:$w_1x_1+w_2x_2,w_2x_2,w_1x_1,0$,然后根据ensemble的精神,把这四个network的output通通都average起来,得到的结果是$\frac{1}{2}(w_1x_1+w_2x_2)$
那根据dropout的想法,我们把从training中得到的参数$w_1,w_2$乘上(1-50%),作为testing network里的参数,也就是$w'_1,w'_2=(1-50\%)(w_1,w_2)=0.5w_1,0.5w_2$
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/dropout11.png" width="60%;" /></center>
这边想要呈现的是,在这个最简单的case里面,用不同的network structure做ensemble这件事情,跟我们用一整个network,并且把weight乘上一个值而不做ensemble所得到的output,其实是一样的
值得注意的是,**只有是linear的network,才会得到上述的等价关系**,如果network是非linear的,ensemble和dropout是不equivalent的;但是,dropout最后一个很神奇的地方是,虽然在non-linear的情况下,它是跟ensemble不相等的,但最后的结果还是会work
==**如果network很接近linear的话,dropout所得到的performance会比较好,而ReLU和Maxout的network相对来说是比较接近于linear的,所以我们通常会把含有ReLU或Maxout的network与Dropout配合起来使用**==

View File

@@ -0,0 +1,264 @@
# Why Deep?
> 本文主要围绕Deep这个关键词展开,重点比较了shallow learning和deep learning的区别:
> shallow:不考虑不同input之间的关联,针对每一种class都设计了一个独立的model检测
> deep:考虑了input之间的某些共同特征,所有class用同个model分类,share参数,modularization思想,hierarchy架构,更有效率地使用data和参数
#### Shallow V.s. Deep
##### Deep is Better?
我们都知道deep learning在很多问题上的表现都是比较好的,越deep的network一般都会有更好的performance
那为什么会这样呢?有一种解释是:
- 一个network的层数越多,参数就越多,这个model就越复杂,它的bias就越小,而使用大量的data可以降低这个model的variance,performance当然就会更好
如下图所示,随着layer层数从1到7,得到的error rate不断地降低,所以有人就认为,deep learning的表现这么好,完全就是用大量的data去硬train一个非常复杂的model而得到的结果
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/deeper1.png" width="60%;" /></center>
既然大量的data加上参数足够多的model就可以实现这个效果,那为什么一定要用DNN呢?我们完全可以用一层的shallow neural network来做同样的事情,理论上只要这一层里neuron的数目足够多,有足够的参数,就可以表示出任何函数;那DNN中deep的意义何在呢?
##### Fat + Short v.s. Thin + Tall
其实深和宽这两种结构的performance是会不一样的,这里我们就拿下面这两种结构的network做一下比较:
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/deeper2.png" width="60%;" /></center>
值得注意的是:如果要给Deep和Shallow的model一个公平的评比,你就要故意调整它们的形状,让它们的参数是一样多的,在这个情况下Shallow的model就会是一个矮胖的model,Deep的model就会是一个瘦高的model
在这个公平的评比之下,得到的结果如下图所示:
左侧表示的是deep network的情况,右侧表示的是shallow network的情况,为了保证两种情况下参数的数量是比较接近的,因此设置了右侧1\*3772和1\*4634这两种size大小,它们分别对应比较左侧5\*2k和7\*2k这两种情况下的network(注意参数数目和neuron的数目并不是等价的)
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/deeper3.png" width="60%;" /></center>
这个时候你会发现,在参数数量接近的情况下,只有1层的network,它的error rate是远大于好几层的network的;这里甚至测试了1\*16k大小的shallow network,把它跟左侧也是只有一层,但是没有那么宽的network进行比较,由于参数比较多所以才略有优势;但是把1\*16k大小的shallow network和参数远比它少的2\*2k大小的deep network进行比较,结果竟然是后者的表现更好
也就是说,只有1层的shallow network的performance甚至都比不过很多参数比它少但层数比它多的deep network,这是为什么呢?
有人觉得deep learning就是一个暴力辗压的方法,我可以弄一个很大很大的model,然后collect一大堆的data,就可以得到比较好的performance;但根据上面的对比可知,deep learning显然是在结构上存在着某种优势,不然无法解释它会比参数数量相同的shallow learning表现得更好这个现象
#### Modularization
##### introduction
DNN结构一个很大的优势是,Modularization(模块化),它用的是结构化的架构
就像写程序一样,shallow network实际上就是把所有的程序都写在了同一个main函数中,所以它去检测不同的class使用的方法是相互独立的;而deep network则是把整个任务分为了一个个小任务,每个小任务又可以不断细分下去,以形成modularization,就像下图一样
在DNN的架构中,实际上每一层layer里的neuron都像是在解决同一个级别的任务,它们的output作为下一层layer处理更高级别任务的数据来源,低层layer里的neuron做的是对不同小特征的检测,高层layer里的neuron则根据需要挑选低层neuron所抽取出来的不同小特征,去检测一个范围更大的特征;neuron就像是一个个classifier ,后面的classifier共享前面classifier的参数
这样做的好处是,低层的neuron输出的信息可以被高层不同的neuron重复使用,而并不需要像shallow network一样,每次在用到的时候都要重新去检测一遍,因此大大降低了程序的复杂度
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/modularization1.png" width="60%;" /></center>
##### example
这里举一个分类的例子,我们要把input的人物分为四类:长头发女生、长头发男生、短头发女生、短头发男生
如果按照shallow network的想法,我们分别独立地train四个classifier(其实就相当于训练四个独立的model),然后就可以解决这个分类的问题;但是这里有一个问题,长头发男生的data是比较少的,没有太多的training data,所以,你train出来的classifier就比较weak,去detect长头发男生的performance就比较差
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/modularization2.png" width="60%;" /></center>
但其实我们的input并不是没有关联的,长头发的男生和长头发的女生都有一个共同的特征,就是长头发,因此如果我们分别**独立地训练四个model作为分类器**,实际上就是忽视了这个共同特征,也就是没有高效地用到data提供的全部信息,这恰恰是shallow network的弊端
而利用modularization的思想,使用deep network的架构,我们可以**训练一个model作为分类器就可以完成所有的任务**,我们可以把整个任务分为两个子任务:
- Classifier1:检测是男生或女生
- Classifier2:检测是长头发或短头发
虽然长头发的男生data很少,但长头发的人的data就很多,经过前面几层layer的特征抽取,就可以头发的data全部都丢给Classifier2,把男生或女生的data全部都丢给Classifier1,这样就真正做到了充分、高效地利用数据,最终的Classifier再根据Classifier1和Classifier2提供的信息给出四类人的分类结果
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/modularization3.png" width="60%;" /></center>
你会发现,经过层层layer的任务分解,其实每一个Classifier要做的事情都是比较简单的,又因为这种分层的、模组化的方式充分利用了data,并提高了信息利用的效率,所以只要用比较少的training data就可以把结果train好
##### deep -> modularization
做modularization的好处是**把原来比较复杂的问题变得简单**,比如原来的任务是检测一个长头发的女生,但现在你的任务是检测长头发和检测性别,而当检测对象变简单的时候,就算training data没有那么多,我们也可以把这个task做好,并且**所有的classifier都用同一组参数检测子特征**,提高了参数使用效率,这就是modularization、这就是模块化的精神
==**由于deep learning的deep就是在做modularization这件事,所以它需要的training data反而是比较少的**==,这可能会跟你的认知相反,AI=big data+deep learning,但deep learning其实是为了解决less data的问题才提出的
每一个neuron其实就是一个basic的classifier:
- 第一层neuron,它是一个最basic的classifier,检测的是颜色、线条这样的小特征
- 第二层neuron是比较复杂的classifier,它用第一层basic的classifier的output当作input,也就是把第一层的classifier当作module,利用第一层得到的小特征分类出不同样式的花纹
- 而第三层的neuron又把第二层的neuron当作它module,利用第二层得到的特征分类出蜂窝、轮胎、人
- 以此类推
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/modularization4.png" width="60%;" /></center>
这边要强调的是,在做deep learning的时候,怎么做模块化这件事情是machine自动学到的,也就是说,第一层要检测什么特征、第二层要检测什么特征...这些都不是人为指定的,人只有定好有几层layer、每层layer有几个neuron,剩下的事情都是machine自己学到的
传统的机器学习算法,是人为地根据domain knowledge指定特征来进行提取,这种指定的提取方式,甚至是提取到的特征,也许并不是实际最优的,所以它的识别成功率并没有那么高;但是如果提取什么特征、怎么提取这件事让机器自己去学,它所提取的就会是那个最优解,因此识别成功率普遍会比人为指定要来的高
#### Speech
前面讲了deep learning的好处来自于modularization(模块化),可以用比较efficient的方式来使用data和参数,这里以语音识别为例,介绍DNN的modularization在语音领域的应用
##### language basics
当你说what do you think的时候,这句话其实是由一串phoneme所组成的,所谓phoneme,中文翻成音素,它是由语言学家制订的人类发音的基本单位,what由4个phoneme组成,do由两个phoneme组成,you由两个phoneme组成,等等
同样的phoneme也可能会有不太一样的发音,当你发d uw和y uw的时候,心里想要发的都是uw,但由于人类发音器官的限制,你的phoneme发音会受到前后的phoneme所影响;所以,为了表达这一件事情,我们会给同样的phoneme不同的model,这个东西就叫做tri-phone
一个phoneme可以拆成几个state,我们通常就订成3个state
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/speech1.png" width="60%;" /></center>
以上就是人类语言的基本构架
##### process
语音辨识的过程其实非常复杂,这里只是讲语音辨识的第一步
你首先要做的事情是把acoustic feature(声学特征)转成state,这是一个单纯的classification的problem
大致过程就是在一串wave form(声音信号)上面取一个window(通常不会取太大,比如250个mini second大小),然后用acoustic feature来描述这个window里面的特性,每隔一个时间段就取一个window,一段声音信号就会变成一串vector sequence,这个就叫做acoustic feature sequence
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/speech2.png" width="60%;" /></center>
你要建一个Classifier去识别acoustic feature属于哪个state,再把state转成phoneme,然后把phoneme转成文字,接下来你还要考虑同音异字的问题...这里不会详细讲述整个过程,而是想要比较一下过去在用deep learning之前和用deep learning之后,在语音辨识上的分类模型有什么差异
##### classification
###### 传统做法
传统的方法叫做HMM-GMM
GMM,即Gaussian Mixture Model ,它假设语音里的**每一个state都是相互独立的**(跟前面长头发的shallow例子很像,也是假设每种情况相互独立),因此属于每个state的acoustic feature都是stationary distribution(静态分布)的,因此我们可以针对每一个state都训练一个GMM model来识别
但这个方法其实不太现实,因为要列举的model数目太多了,一般中英文都有30几、将近40个phoneme,那这边就假设是30个,而在tri-phone里面,每一个phoneme随着contest(上下文)的不同又有变化,假设tri-phone的形式是a-b-c,那总共就有30\*30\*30=27000个tri-phone,而每一个tri-phone又有三个state,每一个state都要很用一个GMM来描述,那参数实在是太多了
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/speech3.png" width="60%;" /></center>
在有deep learning之前的传统处理方法是,让一些不同的state共享同样的model distribution,这件事情叫做Tied-state,其实实际操作上就把state当做pointer(指针),不同的pointer可能会指向同样的distribution,所以有一些state的distribution是共享的,具体哪些state共享distribution则是由语言学等专业知识决定
那这样的处理方法太粗糙了,所以又有人提出了subspace GMM,它里面其实就有modularization、有模块化的影子,它的想法是,我们先找一个Gaussian pool(里面包含了很多不同的Gaussian distribution),每一个state的information就是一个key,它告诉我们这个state要从Gaussian pool里面挑选哪些Gaussian出来
比如有某一个state 1,它挑第一、第三、第五个Gaussian;另一个state 2,它挑第一、第四、第六个Gaussian;如果你这样做,这些state有些时候就可以share部分的Gaussian,那有些时候就可以完全不share Gaussian,至于要share多少Gaussian,这都是可以从training data中学出来的
###### 思考
HMM-GMM的方法,默认把所有的phone或者state都看做是无关联的,对它们分别训练independent model,这其实是不efficient的,它没有充分利用data提供的信息
对人类的声音来说,不同的phoneme都是由人类的发音器官所generate出来的,它们并不是完全无关的,下图画出了人类语言里面所有的元音,这些元音的发音其实就只受到三件事情的影响:
- 舌头的前后位置
- 舌头的上下位置
- 嘴型
比如图中所标英文的5个元音a,e,i,o,u,当你发a到e到i的时候,舌头是由下往上;而i跟u,则是舌头放在前面或放在后面的差别;在图中同一个位置的元音,它们舌头的位置是一样的,只是嘴型不一样
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/speech4.png" width="60%;" /></center>
###### DNN做法
如果采用deep learning的做法,就是去learn一个deep neural network,这个deep neural network的input是一个acoustic feature,它的output就是该feature属于某个state的概率,这就是一个简单的classification problem
那这边最关键的一点是,所有的state识别任务都是用同一个DNN来完成的;值得注意的是DNN并不是因为参数多取胜的,实际上在HMM-GMM里用到的参数数量和DNN其实是差不多的,区别只是GMM用了很多很小的model ,而DNN则用了一个很大的model
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/speech5.png" width="60%;" /></center>
DNN把所有的state通通用同一个model来做分类,会是一种比较有效率的做法,解释如下
我们拿一个hidden layer出来,然后把这个layer里所有neuron的output降维到2维得到下图,每个点的颜色对应着input a,e,i,o,u,神奇的事情发生了:降维图上这5个元音的分布跟右上角元音位置图的分布几乎是一样的
因此,DNN并不是马上就去检测发音是属于哪一个phone或哪一个state,比较lower的layer会先观察人是用什么样的方式在发这个声音,人的舌头位置应该在哪里,是高是低,是前是后;接下来的layer再根据这个结果,去决定现在的发音是属于哪一个state或哪一个phone
这些lower的layer是一个人类发音方式的detector,而所有phone的检测都share这同一组detector的结果,因此最终的这些classifier是share了同一组用来detect发音方式的参数,这就做到了模块化,同一个参数被更多的地方share,因此显得更有效率
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/speech6.png" width="60%;" /></center>
##### result
这个时候就可以来回答之前在[8_Deep Learning]()中提到的问题了
Universality Theorem告诉我们任何的continuous的function都可以用一层足够宽的neural network来实现,在90年代,这是很多人放弃做deep learning的一个原因
但是这个理论只告诉了我们可能性,却没有说明这件事的效率问题;根据上面的几个例子我们已经知道,只用一个hidden layer来描述function其实是没有效率的;当你用multi-layer,用hierarchy structure来描述function的时候,才会是比较有效率的
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/speech7.png" width="60%;" /></center>
#### Analogy
下面用逻辑电路和剪窗花的例子来更形象地描述Deep和shallow的区别
##### Logic Circuit
==**逻辑电路其实可以拿来类比神经网络**==
- 每一个逻辑门就相当于一个neuron
- 只要两级逻辑门就可以表示任何的boolean function;有一个hidden layer的network(input layer+hidden layer共两层)可以表示任何continuous的function
注:逻辑门只要根据input的0、1状态和对应的output分别建立起门电路关系即可建立两级电路
- 实际设计电路的时候,为了节约成本,会进行多级优化,建立起hierarchy架构,如果某一个结构的逻辑门组合被频繁用到的话,其实在优化电路里,这个组合是可以被多个门电路共享的,这样用比较少的逻辑门就可以完成一个电路;在deep neural network里,践行modularization的思想,许多neuron作为子特征检测器被多个classifier所共享,本质上就是参数共享,就可以用比较少的参数就完成同样的function
比较少的参数意味着不容易overfitting,用比较少的data就可以完成同样任务
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/circuits1.png" width="60%;" /></center>
##### 剪窗花
我们之前讲过这个逻辑回归的分类问题,可能会出现下面这种linear model根本就没有办法分类的问题,而当你加了hidden layer的时候,就相当于做了一个feature transformation,把原来的x1,x2转换到另外一个平面,变成x1'、x2'
你会发现,通过这个hidden layer的转换,其实就好像把原来这个平面按照对角线对折了一样,对折后两个蓝色的点就重合在了一起,这个过程跟剪窗花很像:
- 我们在做剪窗花的时候,每次把色纸对折,就相当于把原先的这个多维空间对折了一次来提高维度
- 如果你在某个地方戳一个洞,再把色纸打开,你折了几折,在对应的这些地方就都会有一个洞;这就相当于在折叠后的高维空间上,画斜线的部分是某一个class,不画斜线的部分是另一个class,那你在这个高维空间上的某一个点,就相当于展开后空间上的许多点,由于可以对这个空间做各种各样复杂的对折和剪裁,所以二维平面上无论多少复杂的分类情况,经过多次折叠,不同class最后都可以在一个高维空间上以比较明显的方式被分隔开来
这样做==**既可以解决某些情况下难以分类的问题,又能够以比较有效率的方式充分利用data**==(比如下面这个折纸,高维空间上的1个点等于二维空间上的5个点,相当于1笔data发挥出5笔data的作用)
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/circuits2.png" width="60%;" /></center>
下面举了一个小例子:
左边的图是training data,右边则是1层hidden layer与3层hidden layer的不同network的情况对比,这里已经控制它们的参数数量趋于相同,试验结果是,当training data为10w笔的时候,两个network学到的样子是比较接近原图的,而如果只给2w笔training data,1层hidden layer的情况就完全崩掉了,而3层hidden layer的情况会比较好一些,它其实可以被看作是剪窗花的时候一不小心剪坏了,然后展开得到的结果
注:关于如何得到model学到的图形,可以用固定model的参数,然后对input进行梯度下降,最终得到结果,具体方法见前几章
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/tony1.png" width="60%;" /></center>
#### End-to-end Learning
##### introduction
所谓的End-to-end learning,指的是只给model input和output,而不告诉它中间每一个function要怎么分工,让它自己去学会知道在生产线的每一站,自己应该要做什么事情;在DNN里,就是叠一个很深的neural network,每一层layer就是生产线上的一个站
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/end.png" width="60%;" /></center>
##### Speech Recognition
End-to-end Learning在语音识别上体现的非常明显
在传统的Speech Recognition里,只有最后GMM这个蓝色的block,才是由training data学出来的,前面绿色的生产线部分都是由过去的“五圣先贤”手动制订出来的,其实制订的这些function非常非常的强,可以说是增一分则太肥,减一分则太瘦这样子,以至于在这个阶段卡了将近20年
后来有了deep learning,我们就可以用neural network把DCT、log这些部分取代掉,甚至你从spectrogram开始都拿deep neural network取代掉,也可以得到更好的结果,如果你分析DNN的weight,它其实可以自动学到要做filter bank这件事情(filter bank是模拟人类的听觉器官所制定出来的filter)
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/speech8.png" width="60%;" /></center>
那能不能够叠一个很深很深的neural network,input直接就是time domain上的声音信号,而output直接就是
文字,中间完全不要做feature transform之类,目前的结果是,现在machine做的事情就很像是在做Fourier transform,它学到的极限也只是做到与Fourier feature transform打平而已,或许DFT已经是信号处理的极限了
有关End-to-end Learning在Image Recognition的应用和Speech Recognition很像,这里不再赘述
#### Complex Task
那deep learning还有什么好处呢?
有时候我们会遇到非常复杂的task:
- 有时候非常像的input,它会有很不一样的output
比如在做图像辨识的时候,下图这个白色的狗跟北极熊其实看起来是很像的,但是你的machine要有能力知道,看到左边这张图要output狗,看到右边这张图要output北极熊
- 有时候看起来很不一样的input,output其实是一样的
比如下面这两个方向上看到的火车,横看成岭侧成峰,尽管看到的很不一样,但是你的machine要有能力知道这两个都是同一种东西
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/complex.png" width="60%;" /></center>
如果你的network只有一层的话,就只能做简单的transform,没有办法把一样的东西变得很不一样,把不一样的东西变得很像;如果要实现这些,就需要做很多层次的转换,就像前面那个剪窗花的例子,在二维、三维空间上看起来很难辨别,但是到了高维空间就完全有可能把它们给辨别出来
这里以MNIST手写数字识别为例,展示一下DNN中,在高维空间上对这些Complex Task的处理能力
如果把28\*28个pixel组成的vector投影到二维平面上就像左上角所示,你会发现4跟9的pixel几乎是叠在一起的,因为4跟9很像,都是一个圈圈再加一条线,所以如果你光看input的pixel的话,4跟9几乎是叠在一起的,你几乎没有办法把它分开
但是,等到第二个、第三个layer的output,你会发现4、7、9逐渐就被分开了,所以使用deep learning的deep,这也是其中一个理由
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/task2.png" width="60%;" /></center>
#### Conclusion
Deep总结:
- 考虑input之间的内在关联,所有的class用同一个model来做分类
- modularization思想,复杂问题简单化,把检测复杂特征的大任务分割成检测简单特征的小任务
- 所有的classifier使用同一组参数的子特征检测器,共享检测到的子特征
- 不同的classifier会share部分的参数和data,效率高
- 联系logic circuit和剪纸画的例子
- 多层hidden layer对complex问题的处理上比较有优势

View File

@@ -0,0 +1,316 @@
# Semi-supervised Learning
> 半监督学习(semi-supervised learning)
> 1、introduction
> 2、Semi-supervised Learning for Generative Model
> 3、Low-density Separation Assumption:非黑即白
> 4、Smoothness Assumption:近朱者赤,近墨者黑
> 5、Better Representation:去芜存菁,化繁为简
#### Introduction
Supervised Learning:$(x^r,\hat y^r)$$_{r=1}^R$
- training data中,**每一组**data都有input $x^r$和对应的output $y^r$
Semi-supervised Learning:$\{(x^r,\hat y^r)\}_{r=1}^R$} + $\{x^u\}_{u=R}^{R+U}$
- training data中,部分data没有标签,只有input $x^u$
- 通常遇到的场景是,无标签的数据量远大于有标签的数据量,即**U>>R**
- semi-supervised learning分为以下两种情况:
- Transductive Learning:unlabeled data is the testing data
即,把testing data当做无标签的training data使用,适用于事先已经知道testing data的情况(一些比赛的时候)
值得注意的是,这种方法使用的仅仅是testing data的**feature**,而不是label,因此不会出现“直接对testing data做训练而产生cheating的效果”
- Inductive Learning:unlabeled data is not the testing data
即,不把testing data的feature拿去给机器训练,适用于事先并不知道testing data的情况(更普遍的情况)
- 为什么要做semi-supervised learning?
实际上我们从来不缺data,只是缺有label的data,就像你可以拍很多照片,但它们一开始都是没有标签的
#### Why semi-supervised learning help?
为什么semi-supervised learning会有效呢?
*The distribution of the unlabeled data tell us something.*
unlabeled data虽然只有input,但它的**分布**,却可以告诉我们一些事情
以下图为例,在只有labeled data的情况下,红线是二元分类的分界线
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/semi-help1.png" width="45%;"/></center>
但当我们加入unlabeled data的时候,由于**特征分布**发生了变化,分界线也随之改变
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/semi-help2.png" width="50%;"/></center>
semi-supervised learning的使用往往伴随着假设,而该假设的合理与否,决定了结果的好坏程度;比如上图中的unlabeled data,它显然是一只狗,而特征分布却与猫被划分在了一起,很可能是由于这两张图片的背景都是绿色导致的,因此假设是否合理显得至关重要
#### Semi-supervised Learning for Generative Model
##### Supervised Generative Model
事实上,在监督学习中,我们已经讨论过概率生成模型了,假设class1和class2的分布分别为$mean_1=u^1,covariance_1=\Sigma$、$mean_2=u^2,covariance_2=\Sigma$的高斯分布,计算出Prior Probability后,再根据贝叶斯公式可以推得新生成的x所属的类别
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/super-gm.png" width="60%;"/></center>
##### Semi-supervised Generative Model
如果在原先的数据下多了unlabeled data(下图中绿色的点),它就会影响最终的决定,你会发现原先的$u,\Sigma$显然是不合理的,新的$u,\Sigma$需要使得样本点的分布更接近下图虚线圆所标出的范围,除此之外,右侧的Prior Probability会给人一种比左侧大的感觉(右侧样本点"变多"了)
此时,unlabeled data对$P(C_1),P(C_2),u^1,u^2,\Sigma$都产生了一定程度的影响,划分两个class的decision boundary也会随之发生变化
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/super-semi.png" width="60%;"/></center>
讲完了直观上的解释,接下来进行具体推导(假设做二元分类):
- 先随机初始化一组参数:$\theta=\{P(C_1),P(C_2),u^1,u^2,\Sigma\}$
- step1:利用初始model计算每一笔unlabeled data $x^u$属于class 1的概率$P_{\theta}(C_1|x^u)$
- step2:update model
如果不考虑unlabeled data,则先验概率显然为属于class1的样本点数$N_1$/总的样本点数$N$,即$P(C_1)=\frac{N_1}{N}$
而考虑unlabeled data时,分子还要加上所有unlabeled data属于class 1的概率和,此时它们被看作小数,可以理解为按照概率一部分属于$C_1$,一部分属于$C_2$
$$
P(C_1)=\frac{N_1+\sum_{x^u}P(C_1|x^u)}{N}
$$
同理,对于均值,原先的mean $u_1=\frac{1}{N_1}\sum\limits_{x^r\in C_1} x^r$加上根据概率对$x^u$求和再归一化的结果即可
$$
u_1=\frac{1}{N_1}\sum\limits_{x^r\in C_1} x^r+\frac{1}{\sum_{x^u}P(C_1|x^u)}\sum\limits_{x^u}P(C_1|x^u)x^u
$$
剩余的参数同理,接下来就有了一组新的参数$\theta'$,于是回到step1->step2->step1循环
- 理论上该方法保证是可以收敛的,而一开始给$\theta$的初始值会影响收敛的结果,类似gradient descent
- 上述的step1就是EM algorithm里的E,step2则是M
以上的推导基于的基本思想是,把unlabeled data $x^u$看成是可以划分的,一部分属于$C_1$,一部分属于$C_2$,此时它的概率$P_{\theta}(x^u)=P_{\theta}(x^u|C_1)P(C_1)+P_{\theta}(x^u|C_2)P(C_2)$,也就是$C_1$的先验概率乘上$C_1$这个class产生$x^u$的概率+$C_2$的先验概率乘上$C_2$这个class产生$x^u$的概率
实际上我们在利用极大似然函数更新参数的时候,就利用了该拆分的结果:
$$
logL(\theta)=\sum\limits_{x^r} logP_{\theta}(x^r)+\sum\limits_{x^u}logP_{\theta}(x^u)
$$
#### Low-density Separation Assumption
接下来介绍一种新的方法,它基于的假设是Low-density separation
通俗来讲,就是这个世界是非黑即白的,在两个class的交界处data的密度(density)是很低的,它们之间会有一道明显的鸿沟,此时unlabeled data(下图绿色的点)就是帮助你在原本正确的基础上挑一条更好的boundary
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/bw.png" width="60%;"/></center>
##### Self Training
low-density separation最具代表性也最简单的方法是**self training**
- 先从labeled data去训练一个model $f^*$,训练方式没有限制
- 然后用该$f^*$去对unlabeled data打上label,$y^u=f^*(x^u)$,也叫作pseudo label
- 从unlabeled data中拿出一些data加到labeled data里,至于data的选取需要你自己设计算法来挑选
- 回头再去训练$f^*$,循环即可
注:该方法对Regression是不适用的
实际上,该方法与之前提到的generative model还是挺像的,区别在于:
- Self Training使用的是hard label:假设一笔data强制属于某个class
- Generative Model使用的是soft label:假设一笔data可以按照概率划分,不同部分属于不同class
如果我们使用的是neural network的做法,$\theta^*$是从labeled data中得到的一组参数,此时丢进来一个unlabeled data $x^u$,通过$f^*_{\theta^*}()$后得到$\left [\begin{matrix} 0.7\\ 0.3 \end{matrix}\right ]$,即它有0.7的概率属于class 1,0.3的概率属于class 2
- 如果此时使用hard label,则$x^u$的label被转化成$\left [\begin{matrix}1\\ 0 \end{matrix}\right ]$
- 如果此时使用soft label,则$x^u$的label依旧是$\left [\begin{matrix} 0.7\\ 0.3 \end{matrix}\right ]$
可以看到,在neural network里使用soft label是没有用的,因为把原始的model里的某个点丢回去重新训练,得到的依旧是同一组参数,实际上low density separation就是通过强制分类来提升分类效果的方法
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/self-training.png" width="60%;"/></center>
##### Entropy-based Regularization
该方法是low-density separation的进阶版,你可能会觉得hard label这种直接强制性打标签的方式有些太武断了,而entropy-based regularization则做了相应的改进:$y^u=f^*_{\theta^*}(x^u)$,其中$y^u$是一个**概率分布(distribution)**
由于我们不知道unlabeled data $x^u$的label到底是什么,但如果通过entropy-based regularization得到的分布集中在某个class上的话,那这个model就是好的,而如果分布是比较分散的,那这个model就是不好的,如下图所示:
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/entropy.png" width="60%;"/></center>
接下来的问题是,如何用数值的方法来evaluate distribution的集中(好坏)与否,要用到的方法叫entropy,一个distribution的entropy可以告诉你它的集中程度:
$$
E(y^u)=-\sum\limits_{m=1}^5 y_m^u ln(y_m^u)
$$
对上图中的第1、2种情况,算出的$E(y^u)=0$,而第3种情况,算出的$E(y^u)=-ln(\frac{1}{5})=ln(5)$,可见entropy越大,distribution就越分散,entropy越小,distribution就越集中
因此我们的目标是在labeled data上分类要正确,在unlabeled data上,output的entropy要越小越好,此时就要修改loss function
- 对labeled data来说,它的output要跟正确的label越接近越好,用cross entropy表示如下:
$$
L=\sum\limits_{x^r} C(y^r,\hat y^r)
$$
- 对unlabeled data来说,要使得该distribution(也就是output)的entropy越小越好:
$$
L=\sum\limits_{x^u} E(y^u)
$$
- 两项综合起来,可以用weight来加权,以决定哪个部分更为重要一些
$$
L=\sum\limits_{x^r} C(y^r,\hat y^r) + \lambda \sum\limits_{x^u} E(y^u)
$$
可以发现该式长得很像regularization,这也就是entropy regularization的名称由来
##### Semi-supervised SVM
SVM要做的是,给你两个class的data,去找一个boundary:
- 要有最大的margin,让这两个class分的越开越好
- 要有最小的分类错误
对unlabeled data穷举所有可能的label,下图中列举了三种可能的情况;然后对每一种可能的结果都去算SVM,再找出可以让margin最大,同时又minimize error的那种情况,下图中是用黑色方框标注的情况
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/semi-svm.png" width="60%;"/></center>
SVM paper:Thorsten Joachims, ”*Transductive* *Inference for Text Classification using Support Vector Machines”,* ICML, 1999
当然这么做会存在一个问题,对于n笔unlabeled data,意味着即使在二元分类里也有$2^n$种可能的情况,数据量大的时候,几乎难以穷举完毕,上面给出的paper提出了一种approximate的方法,基本精神是:一开始你先得到一些label,然后每次改一笔unlabeled data的label,看看可不可以让你的objective function变大,如果变大就去改变该label,具体内容详见paper
#### Smoothness Assumption
##### concepts
smoothness assumption的基本精神是:近朱者赤,近墨者黑
粗糙的定义是相似的x具有相同的$\hat y$,精确的定义是:
- x的分布是不平均的
- 如果$x^1$和$x^2$在一个high density region上很接近的话,那么$\hat y^1$和$\hat y^2$就是相同的
也就是这两个点可以在样本点高密度集中分布的区域块中有一条可连接的路径,即 connected by a high density path
假设下图是data的分布,$x^1,x^2,x^3$是其中的三笔data,如果单纯地看x的相似度,显然$x^2$和$x^3$更接近一些,但对于smoothness assumption来说,$x^1$和$x^2$是处于同一块区域的,它们之间可以有一条相连的路径;而$x^2$与$x^3$之间则是“断开”的,没有high density path,因此$x^1$与$x^2$更“像”
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/smooth.png" width="60%;"/></center>
##### digits detection
以手写数字识别为例,对于最右侧的2和3以及最左侧的2,显然最右侧的2和3在pixel上相似度更高一些;但如果把所有连续变化的2都放进来,就会产生一种“不直接相连的相似”,根据Smoothness Assumption的理论,由于2之间有连续过渡的形态,因此第一个2和最后一个2是比较像的,而最右侧2和3之间由于没有过渡的data,因此它们是比较不像的
人脸的过渡数据也同理
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/smooth2.png" width="60%;"/></center>
##### file classification
Smoothness Assumption在文件分类上是非常有用的
假设对天文学(astronomy)和旅行(travel)的文章进行分类,它们各自有专属的词汇,此时如果unlabeled data与label data的词汇是相同或重合(overlap)的,那么就很容易分类;但在真实的情况下,unlabeled data和labeled data之间可能没有任何重复的words,因为世界上的词汇太多了,sparse的分布很难会使overlap发生
但如果unlabeled data足够多,就会以一种相似传递的形式,建立起文档之间相似的桥梁
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/overlap.png" width="60%;"/></center>
##### cluster and then label
在具体实现上,有一种简单的方法是cluster and then label,也就是先把data分成几个cluster,划分class之后再拿去训练,但这种方法不一定会得到好的结果,因为它的假设是你可以把同一个class的样本点cluster在一起,而这其实是没那么容易的
对图像分类来说,如果单纯用pixel的相似度来划分cluster,得到的结果一般都会很差,你需要设计一个很好的方法来描述image(类似Deep Autoencoder的方式来提取feature),这样cluster才会有效果
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/cluster.png" width="60%;"/></center>
##### Graph-based Approach
之前讲的是比较直觉的做法,接下来引入Graph Structure来表达connected by a high density path这件事
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/graph.png" width="60%;"/></center>
我们把所有的data points都建成一个graph,有时候建立vertex之间的关系是比较容易的,比如网页之间的链接关系、论文之间的引用关系;但有时候需要你自己去寻找vertex之间的关系
graph的好坏,对结果起着至关重要的影响,而如何build graph却是一件heuristic的事情,需要凭着经验和直觉来做
- 首先定义两个object $x^i,x^j$之间的相似度 $s(x^i, x^j)$
如果是基于pixel的相似度,performance可能会不太好;建议使用autoencoder提取出来的feature来计算相似度,得到的performance会好一些
- 算完相似度后,就可以建graph了,方式有很多种:
- k nearest neighbor:假设k=3,则每个point与相似度最接近的3个点相连
- e-neighborhood:每个point与相似度超过某个特定threshold e的点相连
- 除此之外,还可以给Edge特定的weight,让它与相似度$s(x^i,x^j)$成正比
- 建议用RBM function来确定相似度:$s(x^i,x^j)=e^{-\gamma||x^i-x^j||^2 }$
这里$x^i,x^j$均为vector,计算它们的Euclidean Distance(欧几里得距离),加上参数后再去exponential
- 至于加exponential,经验上来说通常是可以帮助提升performance的,在这里只有当$x^i,x^j$非常接近的时候,singularity才会大;只要距离稍微远一点,singularity就会下降得很快,变得很小
- 使用exponential的RBM function可以做到只有非常近的两个点才能相连,稍微远一点就无法相连的效果,避免了下图中跨区域相连的情况
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/build-graph.png" width="60%;"/></center>
graph-based approach的基本精神是,在graph上已经有一些labeled data,那么跟它们相连的point,属于同一类的概率就会上升,每一笔data都会去影响它的邻居,而graph带来的最重要的好处是,这个影响是会随着edges**传递**出去的,即使有些点并没有真的跟labeled data相连,也可以被传递到相应的属性
比如下图中,如果graph建的足够好,那么两个被分别label为蓝色和红色的点就可以传递完两张完整的图;从中我们也可以看出,如果想要让这种方法生效,收集到的data一定要足够多,否则可能传递到一半,graph就断掉了,information的传递就失效了
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/graph-nei.png" width="60%;"/></center>
介绍完了如何定性使用graph,接下来介绍一下如何定量使用graph
定量的使用方式是定义label的smoothness,下图中,edge上的数字是weight,$x^i$表达data,$y^i$表示data的label,计算smoothness的方式为:
$$
S=\frac{1}{2}\sum\limits_{i,j} w_{i,j}(y^i-y^j)^2
$$
**我们期望smooth的值越小越好**
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/graph-cal.png" width="60%;"/></center>
当然上面的式子还可以化简,如果把labeled data和unlabeled data的y组成一个(R+U)-dim vector,即
$$
y=\left [\begin{matrix}
...y^i...y^j
\end{matrix} \right ]^T
$$
于是smooth可以改写为:
$$
S=\frac{1}{2}\sum\limits_{i,j} w_{i,j}(y^i-y^j)^2=y^TLy
$$
其中L为(R+U)×(R+U) matrix,成为**Graph Laplacian**, 定义为$L=D-W$
- W:把data point两两之间weight的关系建成matrix,代表了$x^i$与$x^j$之间的weight值
- D:把W的每一个row上的值加起来放在该行对应的diagonal上即可,比如5=2+3,3=2+1,...
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/graph-cal2.png" width="60%;"/></center>
对$S=y^TLy$来说,y是label,是neural network的output,取决于neural network的parameters,因此要在原来仅针对labeled data的loss function中加上这一项,得到:
$$
L=\sum\limits_{x^r}C(y^r,\hat y^r) + \lambda S
$$
$\lambda S$实际上也是一个regularization term
训练目标:
- labeled data的cross entropy越小越好(neural network的output跟真正的label越接近越好)
- smooth S越小越好(neural network的output,不管是labeled还是unlabeled,都要符合Smoothness Assumption的假设)
具体训练的时候,不一定只局限于neural network的output要smooth,可以对中间任意一个hidden layer加上smooth的限制
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/graph-cal3.png" width="60%;"/></center>
#### Better Representation
Better Representation的精神是,去芜存菁,化繁为简
我们观察到的世界是比较复杂的,而在它的背后其实是有一些比较简单的东西,在操控着这个复杂的世界,所以只要你能够看透这个世界的假象,直指它的核心的话,就可以让training变得比较容易
举一个例子,在神雕侠侣中,杨过要在三招之内剪掉樊一翁的胡子,虽然胡子的变化是比较复杂的,但头的变化是有限的,杨过看透了这一件事情就可以把胡子剪掉。在这个例子中,樊一翁的胡子就是original representation,而他的头就是你要找的better representation
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/better-re.png" width="60%;"/></center>
算法具体思路和内容到unsupervised learning的时候再介绍

View File

@@ -0,0 +1,110 @@
# Unsupervised Learning: Introduction
#### Unsupervised Learning
无监督学习(Unsupervised Learning)可以分为两种:
- 化繁为简
- 聚类(Clustering)
- 降维(Dimension Reduction)
- 无中生有(Generation)
对于无监督学习(Unsupervised Learning)来说,我们通常只会拥有$(x,\hat y)$中的$x$或$\hat y$,其中:
- **化繁为简**就是把复杂的input变成比较简单的output,比如把一大堆没有打上label的树图片转变为一棵抽象的树,此时training data只有input $x$,而没有output $\hat y$
- **无中生有**就是随机给function一个数字,它就会生成不同的图像,此时training data没有input $x$,而只有output $\hat y$
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/unsupervised.png" width="60%"/></center>
#### Clustering
##### Introduction
聚类,顾名思义,就是把相近的样本划分为同一类,比如对下面这些没有标签的image进行分类,手动打上cluster 1、cluster 2、cluster 3的标签,这个分类过程就是化繁为简的过程
一个很critical的问题:我们到底要分几个cluster?
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/clustering.png" width="60%"/></center>
##### K-means
最常用的方法是**K-means**:
- 我们有一大堆的unlabeled data $\{x^1,...,x^n,...,x^N\}$,我们要把它划分为K个cluster
- 对每个cluster都要找一个center $c^i,i\in \{1,2,...,K\}$,initial的时候可以从training data里随机挑K个object $x^n$出来作为K个center $c^i$的初始值
- 遍历所有的object $x^n$,并判断它属于哪一个cluster,如果$x^n$与第i个cluster的center $c^i$最接近,那它就属于该cluster,我们用$b_i^n=1$来表示第n个object属于第i个cluster,$b_i^n=0$表示不属于
- 更新center:把每个cluster里的所有object取平均值作为新的center值,即$c^i=\sum\limits_{x^n}b_i^n x^n/\sum\limits_{x^n} b_i^n$
- 反复进行以上的操作
注:如果不是从原先的data set里取center的初始值,可能会导致部分cluster没有样本点
##### HAC
HAC,全称Hierarchical Agglomerative Clustering,层次聚类
假设现在我们有5个样本点,想要做clustering:
- build a tree:
整个过程类似建立Huffman Tree,只不过Huffman是依据词频,而HAC是依据相似度建树
- 对5个样本点两两计算相似度,挑出最相似的一对,比如样本点1和2
- 将样本点1和2进行merge (可以对两个vector取平均),生成代表这两个样本点的新结点
- 此时只剩下4个结点,再重复上述步骤进行样本点的合并,直到只剩下一个root结点
- pick a threshold:
选取阈值,形象来说就是在构造好的tree上横着切一刀,相连的叶结点属于同一个cluster
下图中,不同颜色的横线和叶结点上不同颜色的方框对应着切法与cluster的分法
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/HAC.png" width="60%"/></center>
HAC和K-means最大的区别在于如何决定cluster的数量,在K-means里,K的值是要你直接决定的;而在HAC里,你并不需要直接决定分多少cluster,而是去决定这一刀切在树的哪里
#### Dimension Reduction
##### Introduction
clustering的缺点是**以偏概全**,它强迫每个object都要属于某个cluster
但实际上某个object可能拥有多种属性,或者多个cluster的特征,如果把它强制归为某个cluster,就会失去很多信息;我们应该用一个vector来描述该object,这个vector的每一维都代表object的某种属性,这种做法就叫做Distributed Representation,或者说,Dimension Reduction
如果原先的object是high dimension的,比如image,那现在用它的属性来描述自身,就可以使之从高维空间转变为低维空间,这就是所谓的**降维(Dimension Reduction)**
下图为动漫“全职猎人”中小杰的念能力分布,从表中可以看出我们不能仅仅把他归为强化系
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/DR.png" width="60%"/></center>
##### Why Dimension Reduction Help?
接下来我们从另一个角度来看为什么Dimension Reduction可能是有用的
假设data为下图左侧中的3D螺旋式分布,你会发现用3D的空间来描述这些data其实是很浪费的,因为我们完全可以把这个卷摊平,此时只需要用2D的空间就可以描述这个3D的信息
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/DR2.png" width="60%"/></center>
如果以MNIST(手写数字集)为例,每一张image都是28\*28的dimension,但我们反过来想,大多数28\*28 dimension的vector转成image,看起来都不会像是一个数字,所以描述数字所需要的dimension可能远比28\*28要来得少
举一个极端的例子,下面这几张表示“3”的image,我们完全可以用中间这张image旋转$\theta$角度来描述,也就是说,我们只需要用$\theta$这一个dimension就可以描述原先28\*28 dimension的图像
你只要抓住角度的变化就可以知道28维空间中的变化,这里的28维pixel就是之前提到的樊一翁的胡子,而1维的角度则是他的头,也就是去芜存菁,化繁为简的思想
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/DR3.png" width="60%"/></center>
##### How to do Dimension Reduction?
在Dimension Reduction里,我们要找一个function,这个function的input是原始的x,output是经过降维之后的z
最简单的方法是**Feature Selection**,即直接从原有的dimension里拿掉一些直观上就对结果没有影响的dimension,就做到了降维,比如下图中从$x_1,x_2$两个维度中直接拿掉$x_1$;但这个方法不总是有用,因为很多情况下任何一个dimension其实都不能被拿掉,就像下图中的螺旋卷
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/DR4.png" width="60%"/></center>
另一个常见的方法叫做**PCA**(Principe Component Analysis)
PCA认为降维就是一个很简单的linear function,它的input x和output z之间是linear transform,即$z=Wx$,PCA要做的,就是根据一大堆的x**把W给找出来**(现在还不知道z长什么样子)
关于PCA算法的介绍详见下一篇文章

View File

@@ -0,0 +1,208 @@
# Unsupervised Learning: PCA(Ⅰ)
> 本文将主要介绍PCA算法的数学推导过程
上一篇文章提到,PCA算法认为降维就是一个简单的linear function,它的input x和output z之间是linear transform,即$z=Wx$,PCA要做的,就是根据$x$**把W给找出来**($z$未知)
#### PCA for 1-D
为了简化问题,这里我们假设z是1维的vector,也就是把x投影到一维空间,此时w是一个row vector
$z_1=w^1\cdot x$,其中$w^1$表示$w$的第一个row vector,假设$w^1$的长度为1,即$||w^1||_2=1$,此时$z_1$就是$x$在$w^1$方向上的投影
那我们到底要找什么样的$w^1$呢?
假设我们现在已有的宝可梦样本点分布如下,横坐标代表宝可梦的攻击力,纵坐标代表防御力,我们的任务是把这个二维分布投影到一维空间上
我们希望选这样一个$w^1$,它使得$x$经过投影之后得到的$z_1$分布越大越好,也就是说,经过这个投影后,不同样本点之间的区别,应该仍然是可以被看得出来的,即:
- 我们希望找一个projection的方向,它可以让projection后的variance越大越好
- 我们不希望projection使这些data point通通挤在一起,导致点与点之间的奇异度消失
- 其中,variance的计算公式:$Var(z_1)=\frac{1}{N}\sum\limits_{z_1}(z_1-\bar{z_1})^2, ||w^1||_2=1$,$\bar {z_1}$是$z_1$的平均值
下图给出了所有样本点在两个不同的方向上投影之后的variance比较情况
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/PCA1.png" width="60%"/></center>
#### PCA for n-D
当然我们不可能只投影到一维空间,我们还可以投影到更高维的空间
对$z=Wx$来说:
- $z_1=w^1\cdot x$,表示$x$在$w^1$方向上的投影
- $z_2=w^2\cdot x$,表示$x$在$w^2$方向上的投影
- ...
$z_1,z_2,...$串起来就得到$z$,而$w^1,w^2,...$分别是$W$的第1,2,...个row,需要注意的是,这里的$w^i$必须相互正交,此时$W$是正交矩阵(orthogonal matrix),如果不加以约束,则找到的$w^1,w^2,...$实际上是相同的值
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/PCA2.png" width="60%"/></center>
#### Lagrange multiplier
求解PCA,实际上已经有现成的函数可以调用,此外你也可以把PCA描述成neural network,然后用gradient descent的方法来求解,这里主要介绍用拉格朗日乘数法(Lagrange multiplier)求解PCA的数学推导过程
注:$w^i$和$x$均为列向量,下文中类似$w^i\cdot x$表示的是矢量内积,而$(w^i)^T\cdot x$表示的是矩阵相乘
##### calculate $w^1$
目标:maximize $(w^1)^TSw^1 $,条件:$(w^1)^Tw^1=1$
- 首先计算出$\bar{z_1}$:
$$
\begin{split}
&z_1=w^1\cdot x\\
&\bar{z_1}=\frac{1}{N}\sum z_1=\frac{1}{N}\sum w^1\cdot x=w^1\cdot \frac{1}{N}\sum x=w^1\cdot \bar x
\end{split}
$$
- 然后计算maximize的对象$Var(z-1)$:
其中$Cov(x)=\frac{1}{N}\sum(x-\bar x)(x-\bar x)^T$
$$
\begin{split}
Var(z_1)&=\frac{1}{N}\sum\limits_{z_1} (z_1-\bar{z_1})^2\\
&=\frac{1}{N}\sum\limits_{x} (w^1\cdot x-w^1\cdot \bar x)^2\\
&=\frac{1}{N}\sum (w^1\cdot (x-\bar x))^2\\
&=\frac{1}{N}\sum(w^1)^T(x-\bar x)(x-\bar x)^T w^1\\
&=(w^1)^T\frac{1}{N}\sum(x-\bar x)(x-\bar x)^T w^1\\
&=(w^1)^T Cov(x)w^1
\end{split}
$$
- 当然这里想要求$Var(z_1)=(w^1)^TCov(x)w^1$的最大值,还要加上$||w^1||_2=(w^1)^Tw^1=1$的约束条件,否则$w^1$可以取无穷大
- 令$S=Cov(x)$,它是:
- 对称的(symmetric)
- 半正定的(positive-semidefine)
- 所有特征值(eigenvalues)非负的(non-negative)
- 使用拉格朗日乘数法,利用目标和约束条件构造函数:
$$
g(w^1)=(w^1)^TSw^1-\alpha((w^1)^Tw^1-1)
$$
- 对$w^1$这个vector里的每一个element做偏微分:
$$
\partial g(w^1)/\partial w_1^1=0\\
\partial g(w^1)/\partial w_2^1=0\\
\partial g(w^1)/\partial w_3^1=0\\
...
$$
- 整理上述推导式,可以得到:
其中,$w^1$是S的特征向量(eigenvector)
$$
Sw^1=\alpha w^1
$$
- 注意到满足$(w^1)^Tw^1=1$的特征向量$w^1$有很多,我们要找的是可以maximize $(w^1)^TSw^1$的那一个,于是利用上一个式子:
$$
(w^1)^TSw^1=(w^1)^T \alpha w^1=\alpha (w^1)^T w^1=\alpha
$$
- 此时maximize $(w^1)^TSw^1$就变成了maximize $\alpha$,也就是当$S$的特征值$\alpha$最大时对应的那个特征向量$w^1$就是我们要找的目标
- 结论:**$w^1$是$S=Cov(x)$这个matrix中的特征向量,对应最大的特征值$\lambda_1$**
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/cov.png" width="60%"/></center>
##### calculate $w^2$
在推导$w^2$时,相较于$w^1$,多了一个限制条件:$w^2$必须与$w^1$正交(orthogonal)
目标:maximize $(w^2)^TSw^2$,条件:$(w^2)^Tw^2=1,(w^2)^Tw^1=0$
结论:**$w^2$也是$S=Cov(x)$这个matrix中的特征向量,对应第二大的特征值$\lambda_2$**
- 同样是用拉格朗日乘数法求解,先写一个关于$w^2$的function,包含要maximize的对象,以及两个约束条件
$$
g(w^2)=(w^2)^TSw^2-\alpha((w^2)^Tw^2-1)-\beta((w^2)^Tw^1-0)
$$
- 对$w^2$的每个element做偏微分:
$$
\partial g(w^2)/\partial w_1^2=0\\
\partial g(w^2)/\partial w_2^2=0\\
\partial g(w^2)/\partial w_3^2=0\\
...
$$
- 整理后得到:
$$
Sw^2-\alpha w^2-\beta w^1=0
$$
- 上式两侧同乘$(w^1)^T$,得到:
$$
(w^1)^TSw^2-\alpha (w^1)^Tw^2-\beta (w^1)^Tw^1=0
$$
- 其中$\alpha (w^1)^Tw^2=0,\beta (w^1)^Tw^1=\beta$,
而由于$(w^1)^TSw^2$是vector×matrix×vector=scalar,因此在外面套一个transpose不会改变其值,因此该部分可以转化为:
注:S是symmetric的,因此$S^T=S$
$$
\begin{split}
(w^1)^TSw^2&=((w^1)^TSw^2)^T\\
&=(w^2)^TS^Tw^1\\
&=(w^2)^TSw^1
\end{split}
$$
我们已经知道$w^1$满足$Sw^1=\lambda_1 w^1$,代入上式:
$$
\begin{split}
(w^1)^TSw^2&=(w^2)^TSw^1\\
&=\lambda_1(w^2)^Tw^1\\
&=0
\end{split}
$$
- 因此有$(w^1)^TSw^2=0$,$\alpha (w^1)^Tw^2=0$,$\beta (w^1)^Tw^1=\beta$,又根据
$$
(w^1)^TSw^2-\alpha (w^1)^Tw^2-\beta (w^1)^Tw^1=0
$$
可以推得$\beta=0$
- 此时$Sw^2-\alpha w^2-\beta w^1=0$就转变成了$Sw^2-\alpha w^2=0$,即
$$
Sw^2=\alpha w^2
$$
- 由于$S$是symmetric的,因此在不与$w_1$冲突的情况下,这里$\alpha$选取第二大的特征值$\lambda_2$时,可以使$(w^2)^TSw^2$最大
- 结论:**$w^2$也是$S=Cov(x)$这个matrix中的特征向量,对应第二大的特征值$\lambda_2$**
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/cov2.png" width="60%"/></center>
#### PCA-decorrelation
$z=W\cdot x$
神奇之处在于$Cov(z)=D$,即z的covariance是一个diagonal matrix,推导过程如下图所示
PCA可以让不同dimension之间的covariance变为0,即不同new feature之间是没有correlation的,这样做的好处是,**减少feature之间的联系从而减少model所需的参数量**
如果你把原来的input data通过PCA之后再给其他model使用,那这些model就可以使用简单的形式,而无需考虑不同dimension之间类似$x_1\cdot x_2,x_3\cdot x_5^3,...$这些交叉项,此时model得到简化,参数量大大降低,相同的data量可以得到更好的训练结果,从而可以避免overfitting的发生
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/cov3.png" width="60%"/></center>
本文主要介绍的是PCA的数学推导,如果你理解起来有点困难,那下一篇文章将会从另一个角度解释PCA算法的原理~

View File

@@ -0,0 +1,265 @@
# Unsupervised Learning: PCA(Ⅱ)
> 本文主要从组件和SVD分解的角度介绍PCA,并描述了PCA的神经网络实现方式,通过引入宝可梦、手写数字分解、人脸图像分解的例子,介绍了NMF算法的基本思想,此外,还提供了一些PCA相关的降维算法和论文
#### Reconstruction Component
假设我们现在考虑的是手写数字识别,这些数字是由一些类似于笔画的basic component组成的,本质上就是一个vector,记做$u_1,u_2,u_3,...$,以MNIST为例,不同的笔画都是一个28×28的vector,把某几个vector加起来,就组成了一个28×28的digit
写成表达式就是:$x≈c_1u^1+c_2u^2+...+c_ku^k+\bar x$
其中$x$代表某张digit image中的pixel,它等于k个component的加权和$\sum c_iu^i$加上所有image的平均值$\bar x$
比如7就是$x=u^1+u^3+u^5$,我们可以用$\left [\begin{matrix}c_1\ c_2\ c_3...c_k \end{matrix} \right]^T$来表示一张digit image,如果component的数目k远比pixel的数目要小,那这个描述就是比较有效的
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/bc.png" width="60%"/></center>
实际上目前我们并不知道$u^1$~$u^k$具体的值,因此我们要找这样k个vector,使得$x-\bar x$与$\hat x$越接近越好:
$$
x-\bar x≈c_1u^1+c_2u^2+...+c_ku^k=\hat x
$$
而用未知component来描述的这部分内容,叫做Reconstruction error,即$||(x-\bar x)-\hat x||$
接下来我们就要去找k个vector $u^i$去minimize这个error:
$$
L=\min\limits_{u^1,...,u^k}\sum||(x-\bar x)-(\sum\limits_{i=1}^k c_i u^i) ||_2
$$
回顾PCA,$z=W\cdot x$,实际上我们通过PCA最终解得的$\{w^1,w^2,...,w^k\}$就是使reconstruction error最小化的$\{u^1,u^2,...,u^k\}$,简单证明如下:
- 我们将所有的$x^i-\bar x≈c_1^i u^1+c_2^i u^2+...$都用下图中的矩阵相乘来表示,我们的目标是使等号两侧矩阵之间的差距越小越好
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/re.png" width="60%"/></center>
- 可以使用SVD将每个matrix $X_{m×n}$都拆成matrix $U_{m×k}$、$\Sigma_{k×k}$、$V_{k×n}$的乘积,其中k为component的数目
- 值得注意的是,使用SVD拆解后的三个矩阵相乘,是跟等号左边的矩阵$X$最接近的,此时$U$就对应着$u^i$那部分的矩阵,$\Sigma\cdot V$就对应着$c_k^i$那部分的矩阵
- 根据SVD的结论,组成矩阵$U$的k个列向量(标准正交向量, orthonormal vector)就是$XX^T$最大的k个特征值(eignvalue)所对应的特征向量(eigenvector),而$XX^T$实际上就是$x$的covariance matrix,因此$U$就是PCA的k个解
- 因此我们可以发现,通过PCA找出来的Dimension Reduction的transform,实际上就是把$X$拆解成能够最小化Reconstruction error的component的过程,通过PCA所得到的$w^i$就是component $u^i$,而Dimension Reduction的结果就是参数$c_i$
- 简单来说就是,用PCA对$x$进行降维的过程中,我们要找的投影方式$w^i$就相当于恰当的组件$u^i$,投影结果$z^i$就相当于这些组件各自所占的比例$c_i$
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/svd.png" width="60%"/></center>
- 下面的式子简单演示了将一个样本点$x$划分为k个组件的过程,其中$\left [\begin{matrix}c_1 \ c_2\ ... c_k \end{matrix} \right ]^T$是每个组件的比例;把$x$划分为k个组件即从n维投影到k维空间,$\left [\begin{matrix}c_1 \ c_2\ ... c_k \end{matrix} \right ]^T$也是投影结果
注:$x$和$u_i$均为n维列向量
$$
\begin{split}
&x=
\left [
\begin{matrix}
u_1\ u_2\ ...\ u_k
\end{matrix}
\right ]\cdot
\left [
\begin{matrix}
c_1\\
c_2\\
...\\
c_k
\end{matrix}
\right ]\\ \\
&\left [
\begin{matrix}
x_1\\
x_2\\
...\\
x_n
\end{matrix}
\right ]=\left [
\begin{matrix}
u_1^1\ u_2^1\ ... u_k^1 \\
u_1^2\ u_2^2\ ... u_k^2 \\
...\\
u_1^n\ u_2^n\ ... u_k^n
\end{matrix}
\right ]\cdot
\left [
\begin{matrix}
c_1\\
c_2\\
...\\
c_k
\end{matrix}
\right ]\\
\end{split}
$$
#### NN for PCA
现在我们已经知道,用PCA找出来的$\{w^1,w^2,...,w^k\}$就是k个component $\{u^1,u^2,...,u^k\}$
而$\hat x=\sum\limits_{k=1}^K c_k w^k$,我们要使$\hat x$与$x-\bar x$之间的差距越小越好,我们已经根据SVD找到了$w^k$的值,而对每个不同的样本点,都会有一组不同的$c_k$值
在PCA中我们已经证得,$\{w^1,w^2,...,w^k\}$这k个vector是标准正交化的(orthonormal),因此:
$$
c_k=(x-\bar x)\cdot w^k
$$
这个时候我们就可以使用神经网络来表示整个过程,假设$x$是3维向量,要投影到k=2维的component上:
- 对$x-\bar x$与$w^k$做inner product的过程类似于neural network,$x-\bar x$在3维空间上的坐标就相当于是neuron的input,而$w^1_1$,$w^1_2$,$w^1_3$则是neuron的weight,表示在$w^1$这个维度上投影的参数,而$c_1$则是这个neuron的output,表示在$w^1$这个维度上投影的坐标值;对$w^2$也同理
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pca-nn.png" width="60%"/></center>
- 得到$c_1$之后,再让它乘上$w^1$,得到$\hat x$的一部分
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pca-nn2.png" width="60%"/></center>
- 对$c_2$进行同样的操作,乘上$w^2$,贡献$\hat x$的剩余部分,此时我们已经完整计算出$\hat x$三个分量的值
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pca-nn3.png" width="60%"/></center>
- 此时,PCA就被表示成了只含一层hidden layer的神经网络,且这个hidden layer是线性的激活函数,训练目标是让这个NN的input $x-\bar x$与output $\hat x$越接近越好,这件事就叫做**Autoencoder**
- 注意,通过PCA求解出的$w^i$与直接对上述的神经网络做梯度下降所解得的$w^i$是会不一样的,因为PCA解出的$w^i$是相互垂直的(orgonormal),而用NN的方式得到的解无法保证$w^i$相互垂直,NN无法做到Reconstruction error比PCA小,因此:
- 在linear的情况下,直接用PCA找$W$远比用神经网络的方式更快速方便
- 用NN的好处是,它可以使用不止一层hidden layer,它可以做**deep** autoencoder
#### Weakness of PCA
PCA有很明显的弱点:
- 它是**unsupervised**的,如果我们要将下图绿色的点投影到一维空间上,PCA给出的从左上到右下的划分很有可能使原本属于蓝色和橙色的两个class的点被merge在一起
而LDA则是考虑了labeled data之后进行降维的一种方式,但属于supervised
- 它是**linear**的,对于下图中的彩色曲面,我们期望把它平铺拉直进行降维,但这是一个non-linear的投影转换,PCA无法做到这件事情,PCA只能做到把这个曲面打扁压在平面上,类似下图,而无法把它拉开
对类似曲面空间的降维投影,需要用到non-linear transformation
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pca-weak.png" width="60%"/></center>
#### PCA for Pokemon
这里举一个实际应用的例子,用PCA来分析宝可梦的数据
假设总共有800只宝可梦,每只都是一个六维度的样本点,即vector={HP, Atk, Def, Sp Atk, Sp Def, Speed},接下来的问题是,我们要投影到多少维的空间上?
如果做可视化分析的话,投影到二维或三维平面可以方便人眼观察
实际上,宝可梦的$cov(x)$是6维,最多可以投影到6维空间,我们可以先找出6个特征向量和对应的特征值$\lambda_i$,其中$\lambda_i$表示第i个投影维度的variance有多大(即在第i个维度的投影上点的集中程度有多大),然后我们就可以计算出每个$\lambda_i$的比例,ratio=$\frac{\lambda_i}{\sum\limits_{i=1}^6 \lambda_i}$
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pca-poke.png" width="60%"/></center>
从上图的ratio可以看出$\lambda_5$、$\lambda_6$所占比例不高,即第5和第6个principle component(可以理解为维度)所发挥的作用是比较小的,用这两个dimension做投影所得到的variance很小,投影在这两个方向上的点比较集中,意味着这两个维度表示的是宝可梦的共性,无法对区分宝可梦的特性做出太大的贡献,所以我们只需要利用前4个principle component即可
注意到新的维度本质上就是旧的维度的加权矢量和,下图给出了前4个维度的加权情况,从PC1到PC4这4个principle component都是6维度加权的vector,它们都可以被认为是某种组件,大多数的宝可梦都可以由这4种组件拼接而成,也就是用这4个6维的vector做linear combination的结果
我们来仔细分析一下这些组件:
- 对第一个vector PC1来说,每个值都是正的,因此这个组件在某种程度上代表了宝可梦的强度
- 对第二个vector PC2来说,防御力Def很大而速度Speed很小,这个组件可以增加宝可梦的防御力但同时会牺牲一部分的速度
- 如果将宝可梦仅仅投影到PC1和PC2这两个维度上,则降维后的二维可视化图像如下图所示:
从该图中也可以得到一些信息:
- 在PC2维度上特别大的那个样本点刚好对应着普普(海龟),确实是防御力且速度慢的宝可梦
- 在PC1维度上特别大的那三个样本点则对应着盖欧卡、超梦等综合实力很强的宝可梦
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pca-poke2.png" width="60%"/></center>
- 对第三个vector PC3来说,sp Def很大而HP和Atk很小,这个组件是用生命力和攻击力来换取特殊防御力
- 对第四个vector PC4来说,HP很大而Atk和Def很小,这个组件是用攻击力和防御力来换取生命力
- 同样将宝可梦只投影到PC3和PC4这两个维度上,则降维后得到的可视化图像如下图所示:
该图同样可以告诉我们一些信息:
- 在PC3维度上特别大的样本点依旧是普普,第二名是冰柱机器人,它们的特殊防御力都比较高
- 在PC4维度上特别大的样本点则是吉利蛋和幸福蛋,它们的生命力比较强
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pca-poke3.png" width="60%"/></center>
#### PCA for MNIST
再次回到手写数字识别的问题上来,这个时候我们就可以熟练地把一张数字图像用多个组件(维度)表示出来了:
$$
digit\ image=a_1 w^1+a_2 w^2+...
$$
这里的$w^i$就表示降维后的其中一个维度,同时也是一个组件,它是由原先28×28维进行加权求和的结果,因此$w^i$也是一张28×28的图像,下图列出了通过PCA得到的前30个组件的形状:
注:PCA就是求$Cov(x)=\frac{1}{N}\sum (x-\bar x)(x-\bar x)^T$的前30个最大的特征值对应的特征向量
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pca-mnist.png" width="60%"/></center>
#### PCA for Face
同理,通过PCA找出人脸的前30个组件(维度),如下图所示:
用这些脸的组件做线性组合就可以得到所有的脸
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pca-face.png" width="60%"/></center>
#### What happens to PCA
在对MNIST和Face的PCA结果展示的时候,你可能会注意到我们找到的组件好像并不算是组件,比如MNIST找到的几乎是完整的数字雏形,而Face找到的也几乎是完整的人脸雏形,但我们预期的组件不应该是类似于横折撇捺,眼睛鼻子眉毛这些吗?
如果你仔细思考了PCA的特性,就会发现得到这个结果是可能的
$$
digit\ image=a_1 w^1+a_2 w^2+...
$$
注意到linear combination的weight $a_i$可以是正的也可以是负的,因此我们可以通过把组件进行相加或相减来获得目标图像,这会导致你找出来的component不是基础的组件,但是通过这些组件的加加减减肯定可以获得基础的组件元素
#### NMF
##### Introduction
如果你要一开始就得到类似笔画这样的基础组件,就要使用NMF(non-negative matrix factorization),非负矩阵分解的方法
PCA可以看成对原始矩阵$X$做SVD进行矩阵分解,但并不保证分解后矩阵的正负,实际上当进行图像处理时,如果部分组件的matrix包含一些负值的话,如何处理负的像素值也会成为一个问题(可以做归一化处理,但比较麻烦)
而NMF的基本精神是,强迫使所有组件和它的加权值都必须是正的,也就是说**所有图像都必须由组件叠加得到**:
- Forcing $a_1$, $a_2$...... be non-negative
- additive combination
- Forcing $w_1$, $w_2$...... be non-negative
- More like “parts of digits”
注:关于NMF的具体算法内容可参考paper(公众号回复“NMF”获取pdf):
*Daniel D. Lee and H. Sebastian Seung. "Algorithms for non-negative matrix factorization."Advances in neural information processing systems. 2001.*
##### NMF for MNIST
在MNIST数据集上,通过NMF找到的前30个组件如下图所示,可以发现这些组件都是由基础的笔画构成:
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/nmf-mnist.png" width="60%"/></center>
##### NMF for Face
在Face数据集上,通过NMF找到的前30个组价如下图所示,相比于PCA这里更像是脸的一部分
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/nmf-face.png" width="60%"/></center>
#### More Related Approaches
降维的方法有很多,这里再列举一些与PCA有关的方法:
- Multidimensional Scaling (**MDS**) [Alpaydin, Chapter 6.7]
MDS不需要把每个data都表示成feature vector,只需要知道特征向量之间的distance,就可以做降维,PCA保留了原来在高维空间中的距离,在某种情况下MDS就是特殊的PCA
- **Probabilistic PCA** [Bishop, Chapter 12.2]
PCA概率版本
- **Kernel PCA** [Bishop, Chapter 12.3]
PCA非线性版本
- Canonical Correlation Analysis (**CCA**) [Alpaydin, Chapter 6.9]
CCA常用于两种不同的data source的情况,比如同时对声音信号和唇形的图像进行降维
- Independent Component Analysis (**ICA**)
ICA常用于source separation,PCA找的是正交的组件,而ICA则只需要找“独立”的组件即可
- Linear Discriminant Analysis (**LDA**) [Alpaydin, Chapter 6.8]
LDA是supervised的方式

View File

@@ -0,0 +1,79 @@
# Matrix Factorization
> 本文将通过一个详细的例子分析矩阵分解思想及其在推荐系统上的应用
#### Introduction
接下来介绍**矩阵分解**的思想:有时候存在两种object,它们之间会受到某种共同**潜在因素**(latent factor)的操控,如果我们找出这些潜在因素,就可以对用户的行为进行预测,这也是**推荐系统**常用的方法之一
假设我们现在去调查每个人购买的公仔数目,ABCDE代表5个人,每个人或者每个公仔实际上都是有着傲娇的属性或天然呆的属性
我们可以用vector去描述人和公仔的属性,如果某个人的属性和某个公仔的属性是match的,即他们背后的vector很像(内积值很大),这个人就会偏向于拥有更多这种类型的公仔
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/mf.png" width="60%"/></center>
#### matrix expression
但是,我们没有办法直接观察某个人背后这些潜在的属性,也不会有人在意一个肥宅心里想的是什么,我们同样也没有办法直接得到动漫人物背后的属性;我们目前有的,只是动漫人物和人之间的关系,即每个人已购买的公仔数目,我们要通过这个关系去推测出动漫人物与人背后的潜在因素(latent factor)
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/mf2.png" width="60%"/></center>
我们可以把每个人的属性用vector $r^A$、$r^B$、$r^C$、$r^D$、$r^E$来表示,而动漫人物的属性则用vector $r^1$、$r^2$、$r^3$、$r^4$来表示,购买的公仔数目可以被看成是matrix $X$,对$X$来说,行数为人数,列数为动漫角色的数目
做一个假设:matrix $X$里的每个element,都是属于人的vector和属于动漫角色的vector的内积
比如,$r^A\cdot r^1≈5$,表示$r^A$和$r^1$的属性比较贴近
接下来就用下图所示的矩阵相乘的方式来表示这样的关系,其中$K$为latent factor的数量,这是未知的,需要你自己去调整选择
我们要找一组$r^A$\~$r^E$和$r^1$\~$r^4$,使得右侧两个矩阵相乘的结果与左侧的matrix $X$越接近越好,可以使用SVD的方法求解
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/mf3.png" width="60%"/></center>
#### prediction
但有时候,部分的information可能是会missing的,这时候就难以用SVD精确描述,但我们可以使用梯度下降的方法求解,loss function如下:
$$
L=\sum\limits_{(i,j)}(r^i\cdot r^j-n_{ij})^2
$$
其中$r^i$值的是人背后的latent factor,$r^j$指的是动漫角色背后的latent factor,我们要让这两个vector的内积与实际购买该公仔的数量$n_{ij}$越接近越好,这个方法的关键之处在于,计算上式时,可以跳过missing的数据,最终通过gradient descent求得$r^i$和$r^j$的值
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/mf4.png" width="60%"/></center>
假设latent factor的数目等于2,则人的属性$r^i$和动漫角色的属性$r^j$都是2维的vector,这里实际进行计算后,把属性中较大值标注出来,可以发现:
- 人:A、B属于同一组属性,C、D、E属于同一组属性
- 动漫角色:1、2属于同一组属性,3、4属于同一组属性
- 结合动漫角色,可以分析出动漫角色的第一个维度是天然呆属性,第二个维度是傲娇属性
- 接下来就可以预测未知的值,只需要将人和动漫角色的vector做内积即可
这也是**推荐系统的常用方法**
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/mf5.png" width="60%"/></center>
#### more about matrix factorization
实际上除了人和动漫角色的属性之外,可能还存在其他因素操控购买数量这一数值,因此我们可以将式子更精确地改写为:
$$
r^A\cdot r^1+b_A+b_1≈5
$$
其中$b_A$表示A这个人本身有多喜欢买公仔,$b_1$则表示这个动漫角色本身有多让人想要购买,这些内容是跟属性vector无关的,此时loss function被改写为:
$$
L=\sum\limits_{(i,j)}(r^i\cdot r^j+b_i+b_j-n_{ij})^2
$$
当然你也可以加上一些regularization去对结果做约束
有关Matrix Factorization和推荐系统更多内容的介绍,可以参考paper(公众号回复“推荐系统”获取pdf ):*Matrix Factorization Techniques For Recommender Systems*
#### for Topic Analysis
如果把matrix factorization的方法用在topic analysis上,就叫做LSA(Latent semantic analysis),潜在语义分析
我们只需要把动漫人物换成文章,人换成词汇,表中的值从购买数量换成词频即可,我们可以用词汇的重要性给词频加权,在各种文章中出现次数越多的词汇越不重要,出现次数越少则越重要
这个场景下找出的latent factor可能会是主题(topic),比如某个词汇或某个文档有多少比例是偏向于财经主题、政治主题...
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/mf6.png" width="60%"/></center>

View File

@@ -0,0 +1,123 @@
# Introduction
> define a set of function(model) -> goodness of function -> pick the best function
### Learning Map
下图中,同样的颜色指的是同一个类型的事情
蓝色方块指的是scenario,即学习的情境。通常学习的情境是我们没有办法控制的,比如做reinforcement Learning是因为我们没有data、没有办法来做supervised Learning的情况下才去做的。如果有data,supervised Learning当然比reinforcement Learning要好;因此手上有什么样的data,就决定你使用什么样的scenario
红色方块指的是task,即要解决的问题。你要解的问题,随着你要找的function的output的不同,有输出scalar的regression、有输出options的classification、有输出structured object的structured Learning...
绿色的方块指的是model,即用来解决问题的模型(function set)。在这些task里面有不同的model,也就是说,同样的task,我们可以用不同的方法来解它,比如linear model、Non-linear model(deep Learning、SVM、decision tree、K-NN...)
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/learningMap.png" alt="learning map" width="60%;" /></center>
#### Supervised Learning(监督学习)
supervised learning 需要大量的training data,这些training data告诉我们说,一个我们要找的function,它的input和output之间有什么样的关系
而这种function的output,通常被叫做label(标签),也就是说,我们要使用supervised learning这样一种技术,我们需要告诉机器,function的input和output分别是什么,而这种output通常是通过人工的方式标注出来的,因此称为人工标注的label,它的缺点是需要大量的人工effort
##### Regression(回归)
regression是machine learning的一个task,特点是==通过regression找到的function,它的输出是一个scalar数值==
比如PM2.5的预测,给machine的training data是过去的PM2.5资料,而输出的是对未来PM2.5的预测**数值**,这就是一个典型的regression的问题
##### Classification(分类)
regression和classification的区别是,我们要机器输出的东西的类型是不一样的,在regression里机器输出的是scalar,而classification又分为两类:
###### Binary Classification(二元分类)
在binary classification里,我们要机器输出的是yes or no,是或否
比如G-mail的spam filtering(垃圾邮件过滤器),输入是邮件,输出是该邮件是否是垃圾邮件
###### Multi-class classification(多元分类)
在multi-class classification里,机器要做的是选择题,等于给他数个选项,每一个选项就是一个类别,它要从数个类别里面选择正确的类别
比如document classification(新闻文章分类),输入是一则新闻,输出是这个新闻属于哪一个类别(选项)
##### model(function set) 选择模型
在解任务的过程中,第一步是要选一个function的set,选不同的function set,会得到不同的结果;而选不同的function set就是选不同的model,model又分为很多种:
* Linear Model(线性模型):最简单的模型
* Non-linear Model(非线性模型):最常用的模型,包括:
* **deep learning**
如alpha-go下围棋,输入是当前的棋盘格局,输出是下一步要落子的位置;由于棋盘是19\*19的,因此可以把它看成是一个有19\*19个选项的选择题
* **SVM**
* **decision tree**
* **K-NN**
#### Semi-supervised Learning(半监督学习)
举例:如果想要做一个区分猫和狗的function
手头上有少量的labeled data,它们标注了图片上哪只是猫哪只是狗;同时又有大量的unlabeled data,它们仅仅只有猫和狗的图片,但没有标注去告诉机器哪只是猫哪只是狗
在Semi-supervised Learning的技术里面,这些没有labeled的data,对机器学习也是有帮助的
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/semi-supervised-Learning.png" alt="semi-supervised" width="60%;" /></center>
#### Transfer Learning(迁移学习)
假设一样我们要做猫和狗的分类问题
我们也一样只有少量的有labeled的data;但是我们现在有大量的不相干的data(不是猫和狗的图片,而是一些其他不相干的图片),在这些大量的data里面,它可能有label也可能没有label
Transfer Learning要解决的问题是,这一堆不相干的data可以对结果带来什么样的帮助
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/transfer-Learning.png" alt="transfer" width="60%;" /></center>
#### Unsupervised Learning(无监督学习)
区别于supervised learning,unsupervised learning希望机器学到无师自通,在完全没有任何label的情况下,机器到底能学到什么样的知识
举例来说,如果我们给机器看大量的文章,机器看过大量的文章之后,它到底能够学到什么事情?它能不能学会每个词汇的意思?
学会每个词汇的意思可以理解为:我们要找一个function,然后把一个词汇丢进去,机器要输出告诉你说这个词汇是什么意思,也许他用一个向量来表示这个词汇的不同的特性,不同的attribute
又比如,我们带机器去逛动物园,给他看大量的动物的图片,对于unsupervised learning来说,我们的data中只有给function的输入的大量图片,没有任何的输出标注;在这种情况下,机器该怎么学会根据testing data的输入来自己生成新的图片?
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/unsupervised-Learning.png" width="60%;" /></center>
#### Structured Learning(结构化学习)
在structured Learning里,我们要机器输出的是,一个有结构性的东西
在分类的问题中,机器输出的只是一个选项;在structured类的problem里面,机器要输出的是一个复杂的物件
举例来说,在语音识别的情境下,机器的输入是一个声音信号,输出是一个句子;句子是由许多词汇拼凑而成,它是一个有结构性的object
或者说机器翻译、人脸识别(标出不同的人的名称)
比如**GAN**也是structured Learning的一种方法
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/structured-Learning.png" alt="structured" width="60%;" /></center>
#### Reinforcement Learning(强化学习)
**Supervised Learning**:我们会告诉机器正确的答案是什么 ,其特点是**Learning from teacher**
* 比如训练一个聊天机器人,告诉他如果使用者说了“Hello”,你就说“Hi”;如果使用者说了“Bye bye”,你就说“Good bye”;就好像有一个家教在它的旁边手把手地教他每一件事情
**Reinforcement Learning**:我们没有告诉机器正确的答案是什么,机器最终得到的只有一个分数,就是它做的好还是不好,但他不知道自己到底哪里做的不好,他也没有正确的答案;很像真实社会中的学习,你没有一个正确的答案,你只知道自己是做得好还是不好。其特点是**Learning from critics**
* 比如训练一个聊天机器人,让它跟客人直接对话;如果客人勃然大怒把电话挂掉了,那机器就学到一件事情,刚才做错了,它不知道自己哪里做错了,必须自己回去反省检讨到底要如何改进,比如一开始不应该打招呼吗?还是中间不能骂脏话之类的
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/reinforcement-Learning.png" alt="reinforcement" width="60%;" /></center>
再拿下棋这件事举例,supervised Learning是说看到眼前这个棋盘,告诉机器下一步要走什么位置;而reinforcement Learning是说让机器和对手互弈,下了好几手之后赢了,机器就知道这一局棋下的不错,但是到底哪一步是赢的关键,机器是不知道的,他只知道自己是赢了还是输了
其实Alpha Go是用supervised Learning+reinforcement Learning的方式去学习的,机器先是从棋谱学习,有棋谱就可以做supervised的学习;之后再做reinforcement Learning,机器的对手是另外一台机器,Alpha Go就是和自己下棋,然后不断的进步

View File

@@ -0,0 +1,256 @@
### Unsupervised Learning: Word Embedding
> 本文介绍NLP中词嵌入(Word Embedding)相关的基本知识,基于降维思想提供了count-based和prediction-based两种方法,并介绍了该思想在机器问答、机器翻译、图像分类、文档嵌入等方面的应用
#### Introduction
词嵌入(word embedding)是降维算法(Dimension Reduction)的典型应用
那如何用vector来表示一个word呢?
##### 1-of-N Encoding
最传统的做法是1-of-N Encoding,假设这个vector的维数就等于世界上所有单词的数目,那么对每一个单词来说,只需要某一维为1,其余都是0即可;但这会导致任意两个vector都是不一样的,你无法建立起同类word之间的联系
##### Word Class
还可以把有同样性质的word进行聚类(clustering),划分成多个class,然后用word所属的class来表示这个word,但光做clustering是不够的,不同class之间关联依旧无法被有效地表达出来
##### Word Embedding
词嵌入(Word Embedding)把每一个word都投影到高维空间上,当然这个空间的维度要远比1-of-N Encoding的维度低,假如后者有10w维,那前者只需要50\~100维就够了,这实际上也是Dimension Reduction的过程
类似语义(semantic)的词汇,在这个word embedding的投影空间上是比较接近的,而且该空间里的每一维都可能有特殊的含义
假设词嵌入的投影空间如下图所示,则横轴代表了生物与其它东西之间的区别,而纵轴则代表了会动的东西与静止的东西之间的差别
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/we.png" width="60%"/></center>
word embedding是一个无监督的方法(unsupervised approach),只要让机器阅读大量的文章,它就可以知道每一个词汇embedding之后的特征向量应该长什么样子
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/we2.png" width="60%"/></center>
我们的任务就是训练一个neural network,input是词汇,output则是它所对应的word embedding vector,实际训练的时候我们只有data的input,该如何解这类问题呢?
之前提到过一种基于神经网络的降维方法,Auto-encoder,就是训练一个model,让它的输入等于输出,取出中间的某个隐藏层就是降维的结果,自编码的本质就是通过自我压缩和解压的过程来寻找各个维度之间的相关信息;但word embedding这个问题是不能用Auto-encoder来解的,因为输入的向量通常是1-of-N编码,各维无关,很难通过自编码的过程提取出什么有用信息
#### Word Embedding
##### basic idea
基本精神就是,每一个词汇的含义都可以根据它的上下文来得到
比如机器在两个不同的地方阅读到了“马英九520宣誓就职”、“蔡英文520宣誓就职”,它就会发现“马英九”和“蔡英文”前后都有类似的文字内容,于是机器就可以推测“马英九”和“蔡英文”这两个词汇代表了可能有同样地位的东西,即使它并不知道这两个词汇是人名
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/we3.png" width="60%"/></center>
怎么用这个思想来找出word embedding的vector呢?有两种做法:
- Count based
- Prediction based
#### Count based
假如$w_i$和$w_j$这两个词汇常常在同一篇文章中出现(co-occur),它们的word vector分别用$V(w_i)$和$V(w_j)$来表示,则$V(w_i)$和$V(w_j)$会比较接近
假设$N_{i,j}$是$w_i$和$w_j$这两个词汇在相同文章里同时出现的次数,我们希望它与$V(w_i)\cdot V(w_j)$的内积越接近越好,这个思想和之前的文章中提到的矩阵分解(matrix factorization)的思想其实是一样的
这种方法有一个很代表性的例子是[Glove Vector](http://nlp.stanford.edu/projects/glove/)
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/count-based.png" width="60%"/></center>
#### Prediction based
##### how to do perdition
给定一个sentence,我们要训练一个神经网络,它要做的就是根据当前的word $w_{i-1}$,来预测下一个可能出现的word $w_i$是什么
假设我们使用1-of-N encoding把$w_{i-1}$表示成feature vector,它作为neural network的input,output的维数和input相等,只不过每一维都是小数,代表在1-of-N编码中该维为1其余维为0所对应的word会是下一个word $w_i$的概率
把第一个hidden layer的input $z_1,z_2,...$拿出来,它们所组成的$Z$就是word的另一种表示方式,当我们input不同的词汇,向量$Z$就会发生变化
也就是说,第一层hidden layer的维数可以由我们决定,而它的input又唯一确定了一个word,因此提取出第一层hidden layer的input,实际上就得到了一组可以自定义维数的Word Embedding的向量
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pb.png" width="60%"/></center>
##### Why prediction works
prediction-based方法是如何体现根据词汇的上下文来了解该词汇的含义这件事呢?
假设在两篇文章中,“蔡英文”和“马英九”代表$w_{i-1}$,“宣誓就职”代表$w_i$,我们希望对神经网络输入“蔡英文”或“马英九”这两个词汇,输出的vector中对应“宣誓就职”词汇的那个维度的概率值是高的
为了使这两个不同的input通过NN能得到相同的output,就必须在进入hidden layer之前,就通过weight的转换将这两个input vector投影到位置相近的低维空间上
也就是说,尽管两个input vector作为1-of-N编码看起来完全不同,但经过参数的转换,将两者都降维到某一个空间中,在这个空间里,经过转换后的new vector 1和vector 2是非常接近的,因此它们同时进入一系列的hidden layer,最终输出时得到的output是相同的
因此,词汇上下文的联系就自动被考虑在这个prediction model里面
总结一下,对1-of-N编码进行Word Embedding降维的结果就是神经网络模型第一层hidden layer的输入向量$\left [ \begin{matrix} z_1\ z_2\ ... \end{matrix} \right ]^T$,该向量同时也考虑了上下文词汇的关联,我们可以通过控制第一层hidden layer的大小从而控制目标降维空间的维数
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pb2.png" width="60%"/></center>
##### Sharing Parameters
你可能会觉得通过当前词汇预测下一个词汇这个约束太弱了,由于不同词汇的搭配千千万万,即便是人也无法准确地给出下一个词汇具体是什么
你可以扩展这个问题,使用10个及以上的词汇去预测下一个词汇,可以帮助得到较好的结果
这里用2个词汇举例,如果是一般是神经网络,我们直接把$w_{i-2}$和$w_{i-1}$这两个vector拼接成一个更长的vector作为input即可
但实际上,我们希望和$w_{i-2}$相连的weight与和$w_{i-1}$相连的weight是tight在一起的,简单来说就是$w_{i-2}$与$w_{i-1}$的相同dimension对应到第一层hidden layer相同neuron之间的连线拥有相同的weight,在下图中,用同样的颜色标注相同的weight:
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pb3.png" width="60%"/></center>
如果我们不这么做,那把同一个word放在$w_{i-2}$的位置和放在$w_{i-1}$的位置,得到的Embedding结果是会不一样的,把两组weight设置成相同,可以使$w_{i-2}$与$w_{i-1}$的相对位置不会对结果产生影响
除此之外,这么做还可以通过共享参数的方式有效地减少参数量,不会由于input的word数量增加而导致参数量剧增
##### Formulation
假设$w_{i-2}$的1-of-N编码为$x_{i-2}$,$w_{i-1}$的1-of-N编码为$x_{i-1}$,维数均为$|V|$,表示数据中的words总数
hidden layer的input为向量$z$,长度为$|Z|$,表示降维后的维数
$$
z=W_1 x_{i-2}+W_2 x_{i-1}
$$
其中$W_1$和$W_2$都是$|Z|×|V|$维的weight matrix,它由$|Z|$组$|V|$维的向量构成,第一组$|V|$维向量与$|V|$维的$x_{i-2}$相乘得到$z_1$,第二组$|V|$维向量与$|V|$维的$x_{i-2}$相乘得到$z_2$,...,依次类推
我们强迫让$W_1=W_2=W$,此时$z=W(x_{i-2}+x_{i-1})$
因此,只要我们得到了这组参数$W$,就可以与1-of-N编码$x$相乘得到word embedding的结果$z$
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pb4.png" width="60%"/></center>
##### In Practice
那在实际操作上,我们如何保证$W_1$和$W_2$一样呢?
以下图中的$w_i$和$w_j$为例,我们希望它们的weight是一样的:
- 首先在训练的时候就要给它们一样的初始值
- 然后分别计算loss function $C$对$w_i$和$w_j$的偏微分,并对其进行更新
$$
w_i=w_i-\eta \frac{\partial C}{\partial w_i}\\
w_j=w_j-\eta \frac{\partial C}{\partial w_j}
$$
这个时候你就会发现,$C$对$w_i$和$w_j$的偏微分是不一样的,这意味着即使给了$w_i$和$w_j$相同的初始值,更新过一次之后它们的值也会变得不一样,因此我们必须保证两者的更新过程是一致的,即:
$$
w_i=w_i-\eta \frac{\partial C}{\partial w_i}-\eta \frac{\partial C}{\partial w_j}\\
w_j=w_j-\eta \frac{\partial C}{\partial w_j}-\eta \frac{\partial C}{\partial w_i}
$$
- 这个时候,我们就保证了$w_i$和$w_j$始终相等:
- $w_i$和$w_j$的初始值相同
- $w_i$和$w_j$的更新过程相同
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pb5.png" width="60%"/></center>
如何去训练这个神经网络呢?注意到这个NN完全是unsupervised,你只需要上网爬一下文章数据直接喂给它即可
比如喂给NN的input是“潮水”和“退了”,希望它的output是“就”,之前提到这个NN的输出是一个由概率组成的vector,而目标“就”是只有某一维为1的1-of-N编码,我们希望minimize它们之间的cross entropy,也就是使得输出的那个vector在“就”所对应的那一维上概率最高
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pb6.png" width="60%"/></center>
##### Various Architectures
除了上面的基本形态,Prediction-based方法还可以有多种变形
- CBOW(Continuous bag of word model)
拿前后的词汇去预测中间的词汇
- Skip-gram
拿中间的词汇去预测前后的词汇
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pb7.png" width="60%"/></center>
##### others
尽管word vector是deep learning的一个应用,但这个neural network其实并不是deep的,它就只有一个linear的hidden layer
我们把1-of-N编码输入给神经网络,经过weight的转换得到Word Embedding,再通过第一层hidden layer就可以直接得到输出
其实过去有很多人使用过deep model,但这个task不用deep就可以实现,这样做既可以减少运算量,跑大量的data,又可以节省下训练的时间(deep model很可能需要长达好几天的训练时间)
#### Application
##### Subtraction
*机器问答*
从得到的word vector里,我们可以发现一些原本并不知道的word与word之间的关系
把word vector两两相减,再投影到下图中的二维平面上,如果某两个word之间有类似包含于的相同关系,它们就会被投影到同一块区域
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/we4.png" width="60%"/></center>
利用这个概念,我们可以做一些简单的推论:
- 在word vector的特征上,$V(Rome)-V(Italy)≈V(Berlin)-V(Germany)$
- 此时如果有人问“罗马之于意大利等于柏林之于?”,那机器就可以回答这个问题
因为德国的vector会很接近于“柏林的vector-罗马的vector+意大利的vector”,因此机器只需要计算$V(Berlin)-V(Rome)+V(Italy)$,然后选取与这个结果最接近的vector即可
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/we5.png" width="60%"/></center>
##### Multi-lingual Embedding
*机器翻译*
此外,word vector还可以建立起不同语言之间的联系
如果你要用上述方法分别训练一个英文的语料库(corpus)和中文的语料库,你会发现两者的word vector之间是没有任何关系的,因为Word Embedding只体现了上下文的关系,如果你的文章没有把中英文混合在一起使用,机器就没有办法判断中英文词汇之间的关系
但是,如果你知道某些中文词汇和英文词汇的对应关系,你可以先分别获取它们的word vector,然后再去训练一个模型,把具有相同含义的中英文词汇投影到新空间上的同一个点
接下来遇到未知的新词汇,无论是中文还是英文,你都可以采用同样的方式将其投影到新空间,就可以自动做到类似翻译的效果
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/we6.png" width="60%"/></center>
参考文献:*Bilingual Word Embeddings for Phrase-Based Machine Translation, Will Zou, Richard Socher, Daniel Cer and Christopher Manning, EMNLP, 2013*
##### Multi-domain Embedding
*图像分类*
这个做法不只局限于文字的应用,你也可以对文字+图像做Embedding
假设你已经得到horse、cat和dog这些**词汇**的vector在空间上的分布情况,你就可以去训练一个模型,把一些已知的horse、cat和dog**图片**去投影到和对应词汇相同的空间区域上
比如对模型输入一张图像,使之输出一个跟word vector具有相同维数的vector,使dog图像的映射向量就散布在dog词汇向量的周围,horse图像的映射向量就散布在horse词汇向量的周围...
训练好这个模型之后,输入新的未知图像,根据投影之后的位置所对应的word vector,就可以判断它所属的类别
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/we7.png" width="60%"/></center>
我们知道在做图像分类的时候,很多情况下都是事先定好要分为哪几个具体的类别,再用这几个类别的图像去训练模型,由于我们无法在训练的时候穷尽所有类别的图像,因此在实际应用的时候一旦遇到属于未知类别的图像,这个模型就无能为力了
而使用image+word Embedding的方法,就算输入的图像类别在训练时没有被遇到过,比如上图中的cat,但如果这张图像能够投影到cat的word vector的附近,根据词汇向量与图像向量的对应关系,你自然就可以知道这张图像叫做cat
##### Document Embedding
*文档嵌入*
除了Word Embedding,我们还可以对Document做Embedding
最简单的方法是把document变成bag-of-word,然后用Auto-encoder就可以得到该文档的语义嵌入(Semantic Embedding),但光这么做是不够的
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/se.png" width="60%"/></center>
词汇的顺序代表了很重要的含义,两句词汇相同但语序不同的话可能会有完全不同的含义,比如
- 白血球消灭了传染病——正面语义
- 传染病消灭了白血球——负面语义
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/se2.png" width="60%"/></center>
想要解决这个问题,具体可以参考下面的几种处理方法:
- **Paragraph Vector**: *Le, Quoc, and Tomas Mikolov. "Distributed Representations of Sentences and Documents.“ ICML, 2014*
- **Seq2seq Auto-encoder**: *Li, Jiwei, Minh-Thang Luong, and Dan Jurafsky. "A hierarchical neural autoencoder for paragraphs and documents." arXiv preprint, 2015*
- **Skip Thought**: *Ryan Kiros, Yukun Zhu, Ruslan Salakhutdinov, Richard S. Zemel, Antonio Torralba, Raquel Urtasun, Sanja Fidler, “Skip-Thought Vectors” arXiv preprint, 2015.*
关于**word2vec**,可以参考博客:http://blog.csdn.net/itplus/article/details/37969519

View File

@@ -0,0 +1,194 @@
# Unsupervised Learning: Neighbor Embedding
> 本文介绍了非线性降维的一些算法,包括局部线性嵌入LLE、拉普拉斯特征映射和t分布随机邻居嵌入t-SNE,其中t-SNE特别适用于可视化的应用场景
PCA和Word Embedding介绍了线性降维的思想,而Neighbor Embedding要介绍的是非线性的降维
#### Manifold Learning
样本点的分布可能是在高维空间里的一个流行(Manifold),也就是说,样本点其实是分布在低维空间里面,只是被扭曲地塞到了一个高维空间里
地球的表面就是一个流行(Manifold),它是一个二维的平面,但是被塞到了一个三维空间里
在Manifold中,只有距离很近的点欧氏距离(Euclidean Distance)才会成立,而在下图的S型曲面中,欧氏距离是无法判断两个样本点的相似程度的
而Manifold Learning要做的就是把这个S型曲面降维展开,把塞在高维空间里的低维空间摊平,此时使用欧氏距离就可以描述样本点之间的相似程度
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/manifold.png" width="60%" /></center>
#### Locally Linear Embedding
局部线性嵌入,locally linear embedding,简称**LLE**
假设在原来的空间中,样本点的分布如下所示,我们关注$x^i$和它的邻居$x^j$,用$w_{ij}$来描述$x_i$和$x_j$的关系
假设每一个样本点$x^i$都是可以用它的neighbor做linear combination组合而成,那$w_{ij}$就是拿$x^j$去组合$x^i$时的权重weight,因此找点与点的关系$w_{ij}$这个问题就转换成,找一组使得所有样本点与周围点线性组合的差距能够最小的参数$w_{ij}$:
$$
\sum\limits_i||x^i-\sum\limits_j w_{ij}x^j ||_2
$$
接下来就要做Dimension Reduction,把$x^i$和$x^j$降维到$z^i$和$z^j$,并且保持降维前后两个点之间的关系$w_{ij}$是不变的
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/lle.png" width="60%" /></center>
LLE的具体做法如下:
- 在原先的高维空间中找到$x^i$和$x^j$之间的关系$w_{ij}$以后就把它固定住
- 使$x^i$和$x^j$降维到新的低维空间上的$z^i$和$z^j$
- $z^i$和$z^j$需要minimize下面的式子:
$$
\sum\limits_i||z^i-\sum\limits_j w_{ij}z^j ||_2
$$
- 即在原本的空间里,$x^i$可以由周围点通过参数$w_{ij}$进行线性组合得到,则要求在降维后的空间里,$z^i$也可以用同样的线性组合得到
实际上,LLE并没有给出明确的降维函数,它没有明确地告诉我们怎么从$x^i$降维到$z^i$,只是给出了降维前后的约束条件
在实际应用LLE的时候,对$x^i$来说,需要选择合适的邻居点数目K才会得到好的结果
下图给出了原始paper中的实验结果,K太小或太大得到的结果都不太好,注意到在原先的空间里,只有距离很近的点之间的关系需要被保持住,如果K选的很大,就会选中一些由于空间扭曲才导致距离接近的点,而这些点的关系我们并不希望在降维后还能被保留
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/lle2.png" width="60%" /></center>
#### Laplacian Eigenmaps
##### Introduction
另一种方法叫拉普拉斯特征映射,Laplacian Eigenmaps
之前在semi-supervised learning有提到smoothness assumption,即我们仅知道两点之间的欧氏距离是不够的,还需要观察两个点在high density区域下的距离
如果两个点在high density的区域里比较近,那才算是真正的接近
我们依据某些规则把样本点建立graph,那么smoothness的距离就可以使用graph中连接两个点路径上的edges数来近似
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/le.png" width="60%" /></center>
##### Review for Smoothness Assumption
简单回顾一下在semi-supervised里的说法:如果两个点$x^1$和$x^2$在高密度区域上是相近的,那它们的label $y^1$和$y^2$很有可能是一样的
$$
L=\sum\limits_{x^r} C(y^r,\hat y^r) + \lambda S\\
S=\frac{1}{2}\sum\limits_{i,j} w_{i,j}(y^i-y^j)^2=y^TLy
$$
其中$C(y^r,\hat y^r)$表示labeled data项,$\lambda S$表示unlabeled data项,它就像是一个regularization term,用于判断我们当前得到的label是否是smooth的
其中如果点$x^i$与$x^j$是相连的,则$w_{i,j}$等于相似度,否则为0,$S$的表达式希望在$x^i$与$x^j$很接近的情况下,相似度$w_{i,j}$很大,而label差距$|y^i-y^j|$越小越好,同时也是对label平滑度的一个衡量
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/le2.png" width="60%" /></center>
##### Application in Unsupervised Task
降维的基本原则:如果$x^i$和$x^j$在high density区域上是相近的,即相似度$w_{i,j}$很大,则降维后的$z^i$和$z^j$也需要很接近,总体来说就是让下面的式子尽可能小
$$
S=\frac{1}{2}\sum\limits_{i,j} w_{i,j}(y^i-y^j)^2
$$
注意,与LLE不同的是,这里的$w_{i,j}$表示$x^i$与$x^j$这两点的相似度,上式也可以写成$S=\sum\limits_{i,j} w_{i,j} ||z^i-z^j||_2$
但光有上面这个式子是不够的,假如令所有的z相等,比如令$z^i=z^j=0$,那上式就会直接停止更新
在semi-supervised中,如果所有label $z^i$都设成一样,会使得supervised部分的$\sum\limits_{x^r} C(y^r,\hat y^r)$变得很大,因此lost就会很大,但在这里少了supervised的约束,因此我们需要给$z$一些额外的约束:
- 假设降维后$z$所处的空间为$M$维,则$\{z^1,z^2,...,z^N\}=R^M$,我们希望降维后的$z$占据整个$M$维的空间,而不希望它活在一个比$M$更低维的空间里
- 最终解出来的$z$其实就是Graph Laplacian $L$比较小的特征值所对应的特征向量
这也是Laplacian Eigenmaps名称的由来,我们找的$z$就是Laplacian matrix的特征向量
如果通过拉普拉斯特征映射找到$z$之后再对其利用K-means做聚类,就叫做谱聚类(spectral clustering)
注:有关拉普拉斯图矩阵的相关内容可参考之前的半监督学习笔记:[15_Semi-supervised Learning](https://sakura-gh.github.io/ML-notes/ML-notes-html/15_Semi-supervised-Learning.html)
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/le3.png" width="60%" /></center>
参考文献:*Belkin, M., Niyogi, P. Laplacian eigenmaps and spectral techniques for embedding and clustering. Advances in neural information processing systems . 2002*
#### t-SNE
t-SNE,全称为T-distributed Stochastic Neighbor Embedding,t分布随机邻居嵌入
##### Shortage in LLE
前面的方法**只假设了相邻的点要接近,却没有假设不相近的点要分开**
所以在MNIST使用LLE会遇到下图的情形,它确实会把同一个class的点都聚集在一起,却没有办法避免不同class的点重叠在一个区域,这就会导致依旧无法区分不同class的现象
COIL-20数据集包含了同一张图片进行旋转之后的不同形态,对其使用LLE降维后得到的结果是,同一个圆圈代表同张图像旋转的不同姿态,但许多圆圈之间存在重叠
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/tsne.png" width="60%" /></center>
##### How t-SNE works
做t-SNE同样要降维,在原来$x$的分布空间上,我们需要计算所有$x^i$与$x^j$之间的相似度$S(x^i,x^j)$
然后需要将其做归一化:$P(x^j|x^i)=\frac{S(x^i,x^j)}{\sum_{k\ne i}S(x^i,x^k)}$,即$x^j$与$x^i$的相似度占所有与$x^i$相关的相似度的比例
将$x$降维到$z$,同样可以计算相似度$S'(z^i,z^j)$,并做归一化:$Q(z^j|z^i)=\frac{S'(z^i,z^j)}{\sum_{k\ne i}S'(z^i,z^k)}$
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/tsne2.png" width="60%" /></center>
注意,这里的归一化是有必要的,因为我们无法判断在$x$和$z$所在的空间里,$S(x^i,x^j)$与$S'(z^i,z^j)$的范围是否是一致的,需要将其映射到一个统一的概率区间
我们希望找到的投影空间$z$,可以让$P(x^j|x^i)$和$Q(z^j|z^i)$的分布越接近越好
用于衡量两个分布之间相似度的方法就是**KL散度(KL divergence)**,我们的目标就是让$L$越小越好:
$$
L=\sum\limits_i KL(P(*|x^i)||Q(*|z^i))\\
=\sum\limits_i \sum\limits_jP(x^j|x^i)log \frac{P(x^j|x^i)}{Q(z^j|z^i)}
$$
##### KL Divergence
这里简单补充一下KL散度的基本知识
KL 散度,最早是从信息论里演化而来的,所以在介绍 KL 散度之前,我们要先介绍一下信息熵,信息熵的定义如下:
$$
H=-\sum\limits_{i=1}^N p(x_i)\cdot log\ p(x_i)
$$
其中$p(x_i)$表示事件$x_i$发生的概率,信息熵其实反映的就是要表示一个概率分布所需要的平均信息量
在信息熵的基础上,我们定义KL散度为:
$$
D_{KL}(p||q)=\sum\limits_{i=1}^N p(x_i)\cdot (log\ p(x_i)-log\ q(x_i))\\
=\sum\limits_{i=1}^N p(x_i)\cdot log\frac{p(x_i)}{q(x_i)}
$$
$D_{KL}(p||q)$表示的就是概率$q$与概率$p$之间的差异,很显然,KL散度越小,说明概率$q$与概率$p$之间越接近,那么预测的概率分布与真实的概率分布也就越接近
##### How to use
t-SNE会计算所有样本点之间的相似度,运算量会比较大,当数据量大的时候跑起来效率会比较低
常见的做法是对原先的空间用类似PCA的方法先做一次降维,然后用t-SNE对这个简单降维空间再做一次更深层次的降维,以期减少运算量
值得注意的是,t-SNE的式子无法对新的样本点进行处理,一旦出现新的$x^i$,就需要重新跑一遍该算法,所以**t-SNE通常不是用来训练模型的,它更适合用于做基于固定数据的可视化**
t-SNE常用于将固定的高维数据可视化到二维平面上
##### Similarity Measure
如果根据欧氏距离计算降维前的相似度,往往采用**RBF function** $S(x^i,x^j)=e^{-||x^i-x^j||_2}$,这个表达式的好处是,只要两个样本点的欧氏距离稍微大一些,相似度就会下降得很快
还有一种叫做SNE的方法,它在降维后的新空间采用与上述相同的相似度算法$S'(z^i,z^j)=e^{-||z^i-z^j||_2}$
对t-SNE来说,它在降维后的新空间所采取的相似度算法是与之前不同的,它选取了**t-distribution**中的一种,即$S'(z^i,z^j)=\frac{1}{1+||z^i-z^j||_2}$
以下图为例,假设横轴代表了在原先$x$空间上的欧氏距离或者做降维之后在$z$空间上的欧氏距离,红线代表RBF function,是降维前的分布;蓝线代表了t-distribution,是降维后的分布
你会发现,降维前后相似度从RBF function到t-distribution:
- 如果原先两个点距离($\Delta x$)比较近,则降维转换之后,它们的相似度($\Delta y$)依旧是比较接近的
- 如果原先两个点距离($\Delta x$)比较远,则降维转换之后,它们的相似度($\Delta y$)会被拉得更远
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/tsne3.png" width="60%" /></center>
也就是说t-SNE可以聚集相似的样本点,同时还会放大不同类别之间的距离,从而使得不同类别之间的分界线非常明显,特别适用于可视化,下图则是对MNIST和COIL-20先做PCA降维,再做t-SNE降维可视化的结果:
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/tsne4.png" width="60%" /></center>
#### Conclusion
小结一下,本文主要介绍了三种非线性降维的算法:
- LLE(Locally Linear Embedding),局部线性嵌入算法,主要思想是降维前后,每个点与周围邻居的线性组合关系不变,$x^i=\sum\limits_j w_{ij}x^j$、$z^i=\sum\limits_j w_{ij}z^j$
- Laplacian Eigenmaps,拉普拉斯特征映射,主要思想是在high density的区域,如果$x^i$、$x^j$这两个点相似度$w_{i,j}$高,则投影后的距离$||z^i-z^j||_2$要小
- t-SNE(t-distribution Stochastic Neighbor Embedding),t分布随机邻居嵌入,主要思想是,通过降维前后计算相似度由RBF function转换为t-distribution,在聚集相似点的同时,拉开不相似点的距离,比较适合用在数据固定的可视化领域

View File

@@ -0,0 +1,242 @@
# Unsupervised Learning: Deep Auto-encoder
> 文本介绍了自编码器的基本思想,与PCA的联系,从单层编码到多层的变化,在文字搜索和图像搜索上的应用,预训练DNN的基本过程,利用CNN实现自编码器的过程,加噪声的自编码器,利用解码器生成图像等内容
#### Introduction
**Auto-encoder本质上就是一个自我压缩和解压的过程**,我们想要获取压缩后的code,它代表了对原始数据的某种紧凑精简的有效表达,即降维结果,这个过程中我们需要:
- Encoder(编码器),它可以把原先的图像压缩成更低维度的向量
- Decoder(解码器),它可以把压缩后的向量还原成图像
注意到,Encoder和Decoder都是Unsupervised Learning,由于code是未知的,对Encoder来说,我们手中的数据只能提供图像作为NN的input,却不能提供code作为output;对Decoder来说,我们只能提供图像作为NN的output,却不能提供code作为input
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto.png" width="60%"/></center>
因此Encoder和Decoder单独拿出一个都无法进行训练,我们需要把它们连接起来,这样整个神经网络的输入和输出都是我们已有的图像数据,就可以同时对Encoder和Decoder进行训练,而降维后的编码结果就可以从最中间的那层hidden layer中获取
#### Compare with PCA
实际上PCA用到的思想与之非常类似,**PCA的过程本质上就是按组件拆分,再按组件重构的过程**
在PCA中,我们先把均一化后的$x$根据组件$W$分解到更低维度的$c$,然后再将组件权重$c$乘上组件的反置$W^T$得到重组后的$\hat x$,同样我们期望重构后的$\hat x$与原始的$x$越接近越好
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-pca.png" width="60%"/></center>
如果把这个过程看作是神经网络,那么原始的$x$就是input layer,重构$\hat x$就是output layer,中间组件分解权重$c$就是hidden layer,在PCA中它是linear的,我们通常又叫它瓶颈层(Bottleneck layer)
由于经过组件分解降维后的$c$,维数要远比输入输出层来得低,因此hidden layer实际上非常窄,因而有瓶颈层的称呼
对比于Auto-encoder,从input layer到hidden layer的按组件分解实际上就是编码(encode)过程,从hidden layer到output layer按组件重构实际上就是解码(decode)的过程
这时候你可能会想,可不可以用更多层hidden layer呢?答案是肯定的
#### Deep Auto-encoder
##### Multi Layer
对deep的自编码器来说,实际上就是通过多级编码降维,再经过多级解码还原的过程
此时:
- 从input layer到bottleneck layer的部分都属于$Encoder$
- 从bottleneck layer到output layer的部分都属于$Decoder$
- bottleneck layer的output就是自编码结果$code$
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-deep.png" width="60%"/></center>
注意到,如果按照PCA的思路,则Encoder的参数$W_i$需要和Decoder的参数$W_i^T$保持一致的对应关系,这可以通过给两者相同的初始值并设置同样的更新过程得到,这样做的好处是,可以节省一半的参数,降低overfitting的概率
但这件事情并不是必要的,实际操作的时候,你完全可以对神经网络进行直接训练而不用保持编码器和解码器的参数一致
##### Visualize
下图给出了Hinton分别采用PCA和Deep Auto-encoder对手写数字进行编码解码后的结果,从784维降到30维,可以看出,Deep的自编码器还原效果比PCA要更好
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-deep2.png" width="60%"/></center>
如果将其降到二维平面做可视化,不同颜色代表不同的数字,可以看到
- 通过PCA降维得到的编码结果中,不同颜色代表的数字被混杂在一起
- 通过Deep Auto-encoder降维得到的编码结果中,不同颜色代表的数字被分散成一群一群的
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-visual.png" width="60%"/></center>
#### Text Retrieval
Auto-encoder也可以被用在文字处理上
比如我们要做文字检索,很简单的一个做法是Vector Space Model,把每一篇文章都表示成空间中的一个vector
假设查询者输入了某个词汇,那我们就把该查询词汇也变成空间中的一个点,并计算query和每一篇document之间的内积(inner product)或余弦相似度(cos-similarity)
注:余弦相似度有均一化的效果,可能会得到更好的结果
下图中跟query向量最接近的几个向量的cosine-similarity是最大的,于是可以从这几篇文章中去检索
实际上这个模型的好坏,就取决于从document转化而来的vector的好坏,它是否能够充分表达文章信息
##### Bag-of-word
最简单的vector表示方法是Bag-of-word,维数等于所有词汇的总数,某一维等于1则表示该词汇在这篇文章中出现,此外还可以根据词汇的重要性将其加权;但这个模型是非常脆弱的,对它来说每个词汇都是相互独立的,无法体现出词汇之间的语义(semantic)
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-text.png" width="60%"/></center>
##### Auto-encoder
虽然Bag-of-word不能直接用于表示文章,但我们可以把它作为Auto-encoder的input,通过降维来抽取有效信息,以获取所需的vector
同样为了可视化,这里将Bag-of-word降维到二维平面上,下图中每个点都代表一篇文章,不同颜色则代表不同的文章类型
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-visual2.png" width="60%"/></center>
如果用户做查询,就把查询的语句用相同的方式映射到该二维平面上,并找出属于同一类别的所有文章即可
在矩阵分解(Matrix Factorization)中,我们介绍了LSA算法,它可以用来寻找每个词汇和每篇文章背后的隐藏关系(vector),如果在这里我们采用LSA,并使用二维latent vector来表示每篇文章,得到的可视化结果如上图右下角所示,可见效果并没有Auto-encoder好
#### Similar Image Search
Auto-encoder同样可以被用在图像检索上
以图找图最简单的做法就是直接对输入的图片与数据库中的图片计算pixel的相似度,并挑出最像的图片,但这种方法的效果是不好的,因为单纯的pixel所能够表达的信息太少了
我们需要使用Auto-encoder对图像进行降维和特征提取,并在编码得到的code所在空间做检索
下图展示了Encoder的过程,并给出了原图与Decoder后的图像对比
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-img.png" width="60%"/></center>
这么做的好处如下:
- Auto-encoder可以通过降维提取出一张图像中最有用的特征信息,包括pixel与pixel之间的关系
- 降维之后数据的size变小了,这意味着模型所需的参数也变少了,同样的数据量对参数更少的模型来说,可以训练出更精确的结果,一定程度上避免了过拟合的发生
- Auto-encoder是一个无监督学习的方法,数据不需要人工打上标签,这意味着我们只需简单处理就可以获得大量的可用数据
下图给出了分别以原图的pixel计算相似度和以auto-encoder后的code计算相似度的两种方法在图像检索上的结果,可以看到,通过pixel检索到的图像会出现很多奇怪的物品,而通过code检索到的图像则都是人脸
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-img2.png" width="60%"/></center>
可能有些人脸在原图的pixel上看起来并不像,但把它们投影到256维的空间中却是相像的,可能在投影空间中某一维就代表了人脸的特征,因此能够被检索出来
#### Pre-training DNN
在训练神经网络的时候,我们一般都会对如何初始化参数比较困扰,预训练(pre-training)是一种寻找比较好的参数初始化值的方法,而我们可以用Auto-encoder来做pre-training
以MNIST数据集为例,我们对每层hidden layer都做一次auto-encoder,**使每一层都能够提取到上一层最佳的特征向量**
为了方便表述,这里用$x-z-x$来表示一个自编码器,其中$x$表述输入输出层的维数,$z$表示隐藏层的维数
- 首先使input通过一个$784-1000-784$的自编码器,当该自编码器训练稳定后,就把参数$W^1$固定住,然后将数据集中所有784维的图像都转化为1000维的vector
注意:这里做的不是降维而是升维,当编码后的维数比输入维数要高时,需要注意可能会出现编码前后原封不动的情况,为此需要额外加一个正则项,比如L1 regularization,强迫使code的分布是分散的
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-pre.png" width="60%"/></center>
- 接下来再让这些1000维的vector通过一个$1000-1000-1000$的编码器,当其训练稳定后,再把参数$W^2$固定住,对数据集再做一次转换
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-pre2.png" width="60%"/></center>
- 接下来再用转换后的数据集去训练第三个$1000-500-1000$的自编码器,训练稳定后固定$W^3$,数据集再次更新转化为500维
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-pre3.png" width="60%"/></center>
- 此时三个隐藏层的参数$W^1$、$W^2$、$W^3$就是训练整个神经网络时的参数初始值
- 然后随机初始化最后一个隐藏层到输出层之间的参数$W^4$
- 再用反向传播去调整一遍参数,因为$W^1$、$W^2$、$W^3$都已经是很好的参数值了,这里只是做微调,这个步骤也因此得名为**Find-tune**
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-pre4.png" width="60%"/></center>
由于现在训练机器的条件比以往更好,因此pre-training并不是必要的,但它也有自己的优势
如果你只有大量的unlabeled data和少量的labeled data,那你可以先用这些unlabeled data把$W^1$、$W^2$、$W^3$先初始化好,最后再用labeled data去微调$W^1$~$W^4$即可
因此pre-training在有大量unlabeled data的场景下(如半监督学习)是比较有用的
#### CNN
##### CNN as Encoder
处理图像通常都会用卷积神经网络CNN,它的基本思想是交替使用卷积层和池化层,让图像越来越小,最终展平,这个过程跟Encoder编码的过程其实是类似的
理论上要实现自编码器,Decoder只需要做跟Encoder相反的事即可,那对CNN来说,解码的过程也就变成了交替使用去卷积层和去池化层即可
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-cnn.png" width="60%"/></center>
那什么是去卷积层(Deconvolution)和去池化层(Unpooling)呢?
##### Unpooling
做pooling的时候,假如得到一个4×4的matrix,就把每4个pixel分为一组,从每组中挑一个最大的留下,此时图像就变成了原来的四分之一大小
如果还要做Unpooling,就需要提前记录pooling所挑选的pixel在原图中的位置,下图中用灰色方框标注
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-unpooling.png" width="60%"/></center>
然后做Unpooling,就要把当前的matrix放大到原来的四倍,也就是把2×2 matrix里的pixel按照原先记录的位置插入放大后的4×4 matrix中,其余项补0即可
当然这不是唯一的做法,在Keras中,pooling并没有记录原先的位置,做Unpooling的时候就是直接把pixel的值复制四份填充到扩大后的matrix里即可
##### Deconvolution
实际上,Deconvolution就是convolution
这里以一维的卷积为例,假设输入是5维,过滤器(filter)的大小是3
卷积的过程就是每三个相邻的点通过过滤器生成一个新的点,如下图左侧所示
在你的想象中,去卷积的过程应该是每个点都生成三个点,不同的点对生成同一个点的贡献值相加;但实际上,这个过程就相当于在周围补0之后再次做卷积,如下图右侧所示,两个过程是等价的
卷积和去卷积的过程中,不同点在于,去卷积需要补零且过滤器的weight与卷积是相反的:
- 在卷积过程中,依次是橙线、蓝线、绿线
- 在去卷积过程中,依次是绿线、蓝线、橙线
因此在实践中,做去卷积的时候直接对模型加卷积层即可
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-deconvolution.png" width="60%"/></center>
#### Other Auto-encoder
##### De-noising Auto-encoder
去噪自编码器的基本思想是,把输入的$x$加上一些噪声(noise)变成$x'$,再对$x'$依次做编码(encode)和解码(decode),得到还原后的$y$
值得注意的是,一般的自编码器都是让输入输出尽可能接近,但在去噪自编码器中,我们的目标是让解码后的$y$与加噪声之前的$x$越接近越好
这种方法可以增加系统的鲁棒性,因为此时的编码器Encoder不仅仅是在学习如何做编码,它还学习到了如何过滤掉噪声这件事情
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-noise.png" width="60%"/></center>
参考文献:*Vincent, Pascal, et al. "Extracting and composing robust features with denoising autoencoders." ICML, 2008.*
##### Contractive Auto-encoder
收缩自动编码器的基本思想是,在做encode编码的时候,要加上一个约束,它可以使得:input的变化对编码后得到的code的影响最小化
这个描述跟去噪自编码器很像,只不过去噪自编码器的重点在于加了噪声之后依旧可以还原回原先的输入,而收缩自动编码器的重点在于加了噪声之后能够保持编码结果不变
参考文献:*Rifai, Salah, et al. "Contractive auto-encoders: Explicit invariance during feature extraction.“ Proceedings of the 28th International Conference on Machine Learning (ICML-11). 2011.*
##### Seq2Seq Auto-encoder
在之前介绍的自编码器中,输入都是一个固定长度的vector,但类似文章、语音等信息实际上不应该单纯被表示为vector,那会丢失很多前后联系的信息
Seq2Seq就是为了解决这个问题提出的,具体内容将在RNN部分介绍
#### Generate
在用自编码器的时候,通常是获取Encoder之后的code作为降维结果,但实际上Decoder也是有作用的,我们可以拿它来生成新的东西
以MNIST为例,训练好编码器之后,取出其中的Decoder,输入一个随机的code,就可以生成一张图像
假设将28×28维的图像通过一层2维的hidden layer投影到二维平面上,得到的结果如下所示,不同颜色的点代表不同的数字,然后在红色方框中,等间隔的挑选二维向量丢进Decoder中,就会生成许多数字的图像
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-gene.png" width="60%"/></center>
此外,我们还可以对code加L2 regularization,以限制code分布的范围集中在0附近,此时就可以直接以0为中心去随机采取样本点,再通过Decoder生成图像
观察生成的数字图像,可以发现横轴的维度表示是否含有圆圈,纵轴的维度表示是否倾斜
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/auto-gene2.png" width="60%"/></center>

View File

@@ -0,0 +1,140 @@
# Unsupervised Learning: Generation
> 本文将简单介绍无监督学习中的生成模型,包括PixelRNN、VAE和GAN,以后将会有一个专门的系列介绍对抗生成网络GAN
#### Introduction
正如*Richard Feynman*所说,*“What I cannot create, I do not understand”*,我无法创造的东西,我也无法真正理解,机器可以做猫狗分类,但却不一定知道“猫”和“狗”的概念,但如果机器能自己画出“猫”来,它或许才真正理解了“猫”这个概念
这里将简要介绍:PixelRNN、VAE和GAN这三种方法
#### PixelRNN
##### Introduction
RNN可以处理长度可变的input,它的基本思想是根据过去发生的所有状态去推测下一个状态
PixelRNN的基本思想是每次只画一个pixel,这个pixel是由过去所有已产生的pixel共同决定的
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pixel-rnn.png" width="60%"/></center>
这个方法也适用于语音生成,可以用前面一段的语音去预测接下来生成的语音信号
总之,这种方法的精髓在于根据过去预测未来,画出来的图一般都是比较清晰的
##### pokemon creation
用这个方法去生成宝可梦,有几个tips:
- 为了减少运算量,将40×40的图像截取成20×20
- 如果将每个pixel都以[R, G, B]的vector表示的话,生成的图像都是灰蒙蒙的,原因如下:
- 亮度比较高的图像,一般都是RGB值差距特别大而形成的,如果各个维度的值大小比较接近,则生成的图像偏向于灰色
- 如果用sigmoid function,最终生成的RGB往往都是在0.5左右,导致色彩度不鲜艳
- 解决方案:将所有色彩集合成一个1-of-N编码,由于色彩种类比较多,因此这里先对类似的颜色做clustering聚类,最终获得了167种色彩组成的向量
我们用这样的向量去表示每个pixel,可以让生成的色彩比较鲜艳
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pixel-rnn-pokemon.png" width="60%"/></center>
相关数据连接如下:
- 原始图像(40\*40)数据的[链接](http://speech.ee.ntu.edu.tw/~tlkagk/courses/ML_2016/Pokemon_creation/image.rar)
- 裁剪后的图像(20\*20)数据[链接](http://speech.ee.ntu.edu.tw/~tlkagk/courses/ML_2016/Pokemon_creation/pixel_color.txt)
- 数值与色彩(RGB)的映射关系[链接](http://speech.ee.ntu.edu.tw/~tlkagk/courses/ML_2016/Pokemon_creation/colormap.txt)
使用PixelRNN训练好模型之后,给它看没有被放在训练集中的3张图像的一部分,分别遮住原图的50%和75%,得到的原图和预测结果的对比如下:
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/pixel-rnn-pokemon2.png" width="60%"/></center>
#### VAE
VAE全称Variational Autoencoder,可变自动编码器
##### Introduction
前面的文章中已经介绍过Autoencoder的基本思想,我们拿出其中的Decoder,给它随机的输入数据,就可以生成对应的图像
但普通的Decoder生成效果并不好,VAE可以得到更好的效果
在VAE中,code不再直接等于Encoder的输出,这里假设目标降维空间为3维,那我们使Encoder分别输出$m_1,m_2,m_3$和$\sigma_1,\sigma_2,\sigma_3$,此外我们从正态分布中随机取出三个点$e_1,e_2,e_3$,将下式作为最终的编码结果:
$$
c_i = e^{\sigma_i}\cdot e_i+m_i
$$
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/vae.png" width="60%"/></center>
此时,我们的训练目标不仅要最小化input和output之间的差距,还要同时最小化下式:
$$
\sum\limits_{i=1}^3 (1+\sigma_i-(m_i)^2-e^{\sigma_i})
$$
与PixelRNN不同的是,VAE画出的图一般都是不太清晰的,但在某种程度上我们可以控制生成的图像
##### write poetry
VAE还可以用来写诗,我们只需要得到某两句话对应的code,然后在降维后的空间中得到这两个code所在点的连线,从中取样,并输入给Decoder,就可以得到类似下图中的效果
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/vae-poetry.png" width="60%"/></center>
##### Why VAE?
VAE和传统的Autoencoder相比,有什么优势呢?
事实上,VAE就是加了噪声noise的Autoencoder,它的抗干扰能力更强,过渡生成能力也更强
对原先的Autoencoder来说,假设我们得到了满月和弦月的code,从两者连线中随机获取一个点并映射回原来的空间,得到的图像很可能是完全不一样的东西
而对VAE来说,它要保证在降维后的空间中,加了noise的一段范围内的所有点都能够映射到目标图像,如下图所示,当某个点既被要求映射到满月、又被要求映射到弦月,则它最终映射出来的结果就很有可能是两者之间的过渡图像
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/vae-why.png" width="60%"/></center>
再回过来头看VAE的结构,其中:
- $m_i$其实就代表原来的code
- $c_i$则代表加了noise以后的code
- $\sigma_i$代表了noise的variance,描述了noise的大小,这是由NN学习到的参数
注:使用$e^{\sigma_i}$的目的是保证variance是正的
- $e_i$是正态分布中随机采样的点
注意到,损失函数仅仅让input和output差距最小是不够的,因为variance是由机器自己决定的,如果不加以约束,它自然会去让variance=0,这就跟普通的Autoencoder没有区别了
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/vae-why2.png" width="60%"/></center>
额外加的限制函数解释如下:
下图中,蓝线表示$e^{\sigma_i}$,红线表示$1+\sigma_i$,两者相减得到绿线
绿线的最低点$\sigma_i=0$,则variance $e^{\sigma_i}=1$,此时loss最低
而$(m_i)^2$项则是对code的L2 regularization,让它比较sparse,不容易过拟合
<center><img src="https://gitee.com/Sakura-gh/ML-notes/raw/master/img/vae-why3.png" width="60%"/></center>
关于VAE原理的具体推导比较复杂,这里不再列出
##### problems of VAE
VAE有一个缺点,它只是在努力做到让生成的图像与数据集里的图像尽可能相似,却从来没有想过怎么样真的产生一张新的图像,因此由VAE生成的图像大多是数据集中图像的线性变化,而很难自主生成全新的图像
VAE做到的只是模仿,而不是创造,GAN的诞生,就是为了创造
#### GAN
GAN,对抗生成网络,是近两年非常流行的神经网络,基本思想就像是天敌之间相互竞争,相互进步
GAN由生成器(Generator)和判别器(Discriminator)组成:
- 对判别器的训练:把生成器产生的图像标记为0,真实图像标记为1,丢给判别器训练分类
- 对生成器的训练:调整生成器的参数,使产生的图像能够骗过判别器
- 每次训练调整判别器或生成器参数的时候,都要固定住另一个的参数
GAN的问题:没有明确的训练目标,很难调整生成器和判别器的参数使之始终处于势均力敌的状态,当两者之间的loss很小的时候,并不意味着训练结果是好的,有可能它们两个一起走向了一个坏的极端,所以在训练的同时还必须要有人在旁边关注着训练的情况
以后将会有GAN系列的文章介绍,本文不再做详细说明

Some files were not shown because too many files have changed in this diff Show More