第三節課程,介紹的是迭代法。
前兩節筆記的文章:
03 迭代法
什麼是迭代法
迭代法,簡單來說,其實就是不斷地用舊的變數值,遞推計算新的變數值。
這裡採用一個故事來介紹什麼是迭代法,這個故事是講述一個國王要重賞一個做出巨大貢獻的臣子,讓臣子提出他想得到的賞賜,這個聰明的臣子說出了他想得到的賞賜--在棋盤上放滿麥子,但要求是每個格子的麥子數量都是前一個格子的兩倍。國王本以為這個賞賜可以輕而易舉的滿足,但真正開始放麥子後,發現即便是拿出全國的糧食也無法滿足的臣子的這個賞賜。
這裡我們可以用f(n)
表示當前各自的麥子數量,而前一個格子的麥子數量就是f(n-1)
,那麼臣子的要求就可以這麼表示:
f(n) = f(n-1) * 2
f(1) = 1
複製程式碼
這也就是迭代法了,而如果用程式設計來實現,其實就是實現一個迴圈運算的過程。
用 Python 實現這個計算麥子的程式碼如下所示:
def get_number_of_wheat(grid):
'''
\計算放到給定格子數量需要的麥子數量
:param grid: 格子數
:return:
'''
# f(1) = 1
wheat_numbers = 1
sums = wheat_numbers
for i in range(2, grid+1):
wheat_numbers *= 2
sums += wheat_numbers
print('when grid = %d, wheats numbers = %d' % (grid, sums))
return sums
複製程式碼
簡單的測試例子:
if __name__ == '__main__':
print('compute numbers of wheat!')
numbers_grid = 63
get_number_of_wheat(numbers_grid)
print('finish')
複製程式碼
給定格子數量是 63 個,輸出結果如下:
compute numbers of wheat!
when grid = 63, wheats numbers = 9223372036854775807
finish
複製程式碼
所以這個天文數字是 19 位數--9223372036854775807,真的是非常的多!假設一袋 50 斤的麥子估計有 130 萬粒麥子,那麼這個計算結果是相當於 70949 億袋 50 斤的麥子!
迭代法的應用
看完上述例子,相信應該對迭代法的基本概念比較瞭解了,而迭代法的基本步驟也很簡單,分為三個步驟:
- 確定用於迭代的變數。上述例子中,這個迭代變數就是
f(n)
和f(n-1)
- 建立迭代變數之間的遞推關係。上述例子中,這個遞迴關係是
f(n)=f(n-1)*2
- 控制迭代的過程。這裡需要確定迭代的初始條件和終止條件,上述例子,初始條件就是
f(1)=1
,而終止條件就是達到給定的格子數了。
那麼迭代法有什麼應用呢?
其實,它在數學和計算機領域都有很廣泛的應用,如:
- 求數值的精確或者近似解。典型的方法包括二分法(Bisection method)和牛頓迭代法(Newton's method);
- 在一定範圍內查詢目標值。典型方法包括二分查詢,其實也是二分法在搜尋方面的應用;
- 機器學習演算法中的迭代。比如 Kmeans 聚類演算法(不斷迭代來對資料進行聚類)、馬爾科夫鏈(Markov chain)、梯度下降法(Gradient descent)等。迭代法在機器學習中有廣泛的應用,其實是因為機器學習的過程,就是根據已知資料和一定的假設,求一個區域性最優解。迭代法可以幫助學習演算法逐步搜尋,直到發現這種解。
接下來會重點介紹求數值的解和查詢匹配記錄,這兩個應用其實都是採用二分法來實現。
求方程的精確或者近似解
迭代法除了用於計算龐大的數字,還可以幫助我們進行無窮次地逼近,求得方程的精確或者近似解。
舉個例子,我們要計算一個給定的正整數n(n>1)
的平方根,並且不能採用程式語言自帶的函式,應該如何計算呢?
首先我們可以明確的是,對於給定的正整數n
,它的平方根肯定是小於它,但大於1,也就是這個平方根的取值範圍是 1 到 n
,在這個範圍內求一個數值的平方等於n
。
這裡就可以通過採用剛剛說的二分法。每次檢視區間內的中間值,檢查它是否符合標準。
比如我們要求 10 的平方根,尋找的區間就是[1,10]
,第一個中間值就是(1+10)/2=11/2=5.5
,而 5.5 的平方等於 30.25,明顯比 10 大,所以尋找區間變成 5.5 的左側,也就是[1, 5.5]
,中間值就是 3.25,但 3.25 的平方是 10.5625,依然大於 10,尋找區間變為[1, 3.25]
,中間值變為 2.125, 2.125 的平方是 4.515625,小於 10,所以區間就是[2.125, 3.25]
,這樣繼續尋找和計算中間值的平方,直到發現某個數的平方正好是 10。
具體步驟如下圖:
這裡用程式碼實現,如下圖所示:
def get_square_root(n, threshold, max_try):
'''
計算大於 1 的正整數的平方根
:param n: 給定正整數
:param threshold: 誤差的閾值
:param max_try: 最大嘗試次數
:return:
'''
if n <= 1:
return -1.0
# interval boundary 區間的左右邊界
left = 1.0
right = float(n)
for idx in range(max_try):
# 防止溢位
middle = left + (right - left) / 2
square = middle * middle
# 誤差
delta = abs(square / n - 1)
if delta <= threshold:
return middle
else:
if square > n:
right = middle
else:
left = middle
return -2.0
複製程式碼
簡單的測試例子:
square_root = get_square_root(10, 0.000001, 10000)
if square_root == -1.0:
print('please input a number > 1')
elif square_root == -2.0:
print('cannot find the square root')
else:
print('square root==', square_root)
複製程式碼
輸出結果是:
square root== 3.1622767448425293
複製程式碼
這裡程式碼中,設定了兩個控制迭代結束的引數:
threshold
:誤差的閾值,用於控制解的精度。理論上二分法可以通過無限次迭代求到精確解,但實際應用還需要考慮時間和計算資源,所以一般我們只需要一個近似解,而不需要完全精確的資料;max_try
:控制迭代的次數。設定這個引數也是為了避免使用while True
迴圈可能導致的死迴圈,當然理論上設定了threshold
是可以避免死迴圈的,但這是一個良好的程式設計習慣,主動避免產生的可能性。
查詢匹配記錄
二分法通過迭代式逼近,不僅可以求得方程的近似解,還可以幫助查詢匹配的記錄。
這裡老師給的例子是在自然語言處理中,處理同義詞或者近義詞的擴充套件問題。這時,你是會有一個詞典,用於記錄每個單詞的同義詞或者近義詞。對於一個待查詢單詞,我們需要在字典找到這個單詞,以及對應的所有同義詞和近義詞,然後進行擴充,例如對於單詞--西紅柿
,它的同義詞包括了番茄
和tomato
。
詞典如下表格所示:
詞條 | 同義詞1 | 同義詞2 | 同義詞3 |
---|---|---|---|
西紅柿 | 番茄 | tomato | ... |
... | ... | ... | ... |
當處理文章的時候,遇到“西紅柿”這個單詞,就在字典裡查詢,返回“番茄”和“tomato"等同義詞或者近義詞,並新增到文章作為同義詞/近義詞的擴充。
這裡要解決的問題就是如何在字典查詢匹配單詞的問題。一種做法就是雜湊表。而如果不用雜湊表的方法,還可以採用二分查詢法。二分查詢法進行字典查詢的思路如下:
- 對整個字典先進行排序(假設是從小到大)。二分法的一個關鍵前提條件就是所查詢區間必須是有序的,這樣每次折半的時候,可以知道是往左還是右繼續查詢。
- 使用二分法逐步定位到被查詢的單詞。同樣是每次都選擇查詢區間的中間值,判斷是否和待查詢單詞一致,如果一致就返回;如果不一致,就進行判斷大小,如果比待查詢單詞小,就需要往中間值右邊區間查詢;否則就在左邊區間查詢。
- 重複第二步操作,迭代式查詢,直到找到單詞,或者沒有找到,就返回不存在。
相比於利用二分法查詢方程解,二分查詢必須要求資料是有序的!
用程式碼實現如下:
def search_word(dictionary, word):
'''
查詢匹配單詞
:param dictionary: 排序後的字典
:param word:待查詢單詞
:return:
'''
if dictionary is None:
return False
if len(dictionary) < 1:
return False
left = 0
right = len(dictionary) - 1
while left <= right:
middle = int(left + (right - left) / 2)
if dictionary[middle] == word:
return True
else:
if dictionary[middle] > word:
right = middle - 1
else:
left = middle + 1
return False
複製程式碼
簡單的測試程式碼:
print('find word in dictionary')
dict_list = ['i', 'am', 'coder']
dict_list = sorted(dict_list)
print('sorted dict:', dict_list)
word_to_find = 'am'
found = search_word(dict_list, word_to_find)
if found:
print('word "%s" found in dictionary--%s!' % (word_to_find, dict_list))
else:
print('cannot find the word "%s"' % word_to_find)
複製程式碼
輸出結果:
find word in dictionary
sorted dict: ['am', 'coder', 'i']
word "am" found in dictionary--['am', 'coder', 'i']!
finish
複製程式碼
迭代法的介紹就到這裡了!上述原始碼地址:
歡迎關注我的微信公眾號--機器學習與計算機視覺,或者掃描下方的二維碼,大家一起交流,學習和進步!