资料搜集

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来完成
## 进度
Exercise1Linear Regression
Exercise2Logistic Regression
Exercise3Multi-class Classification and Neural Networks
Exercise4Neural Networks Learning
Exercise5Regularized Linear Regression and Bias v.s. Variance
Exercise6Support Vector Machines
Exercise7K-means Clustering and Principal Component Analysis
Exercise8Anomaly 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 learningKeras其实是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的Datahttp://yann.lecun.com/exdb/mnist/
Keras提供了自动下载MNIST data的functionhttp://keras.io/datasets/
#### process
首先要先导入keras包`from keras.models import Sequential`
##### step 1define 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的outputKeras自己是知道这件事情的所以你就直接告诉它说新加的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 2goodness 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 3pick 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分成一个个不同的batchKeras会自动帮你完成随机选择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和设10update参数的次数几乎是一样的
如果不同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 settesting 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 dataoutput就直接是分类的结果
~~~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分别就对应着右侧编号123的pixel编号789的pixel跟编号131415的pixel
如果我们说这个filter和image matrix做inner product以后得到的output 3就是input vector经过某个neuron得到的output 3的话这就代表说存在这样一个neuron这个neuron带weight的连线就只连接到编号为123789131415的这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 234pixel 8910跟pixel 141516
<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
相较于convolutionmax 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层
#### AppendixCNN 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 doWhy CNNHow 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的cubicfilter实际在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某一种patterndetect某一种线条上图所示的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一张imageDeep 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的vectorvector的每一个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做convolutionstride设为1activation function用的是ReLU得到的output是21\*21的image接下来使用k个3\*3的filterstride设为1activation function还是使用ReLU...
你会发现这个AlphaGo的network structure一直在用convolution其实**根本就没有使用Max Pooling**原因并不是疏失了什么之类的而是根据围棋的特性我们本来就不需要在围棋的CNN里面用Max pooling这样的构架
举这个例子是为了告诉大家:
==**neural network架构的设计是应用之道存乎一心**==
#### More Application——Speech、Text
##### Speech
CNN也可以用在很多其他的task里面比如语音处理上我们可以把一段声音表示成spectrogramspectrogram的横轴是时间纵轴则是这一段时间里声音的频率
下图中是一段“你好”的音频偏红色代表这段时间里该频率的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 CNNWhat does CNN doWhy 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 functionReLU、Maxout
> adaptive learning rateAdagrad、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 neighbordecision 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为inputa为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** networklinear的好处是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=inputnetwork整体上就变成了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组成的vectoroutput则取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
除了ReLUMaxout还可以实现更多不同的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函数无法微分但是只要真的丢进去了一笔datanetwork就会马上根据这笔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就是+1w是负数的话这个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每次下降一个固定的valuetrain出来的参数是比较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它只有一个neuronactivation 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的varianceperformance当然就会更好
如下图所示随着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就会是一个矮胖的modelDeep的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个元音aeiou当你发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 aeiou神奇的事情发生了降维图上这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把原来的x1x2转换到另外一个平面变成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 data1层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 networkinput直接就是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北极熊
- 有时候看起来很不一样的inputoutput其实是一样的
比如下面这两个方向上看到的火车横看成岭侧成峰尽管看到的很不一样但是你的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 Learningunlabeled data is the testing data
把testing data当做无标签的training data使用适用于事先已经知道testing data的情况(一些比赛的时候)
值得注意的是这种方法使用的仅仅是testing data的**feature**而不是label因此不会出现“直接对testing data做训练而产生cheating的效果”
- Inductive Learningunlabeled 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)$
- step2update 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里的Estep2则是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 10.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 paperThorsten 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是原始的xoutput是经过降维之后的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 separationPCA找的是正交的组件而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的情况下才去做的。如果有datasupervised 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就是选不同的modelmodel又分为很多种
* 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 learningunsupervised 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 networkinput是词汇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的inputoutput的维数和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 Embeddingt分布随机邻居嵌入
##### 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