程式設計師的數學筆記3--迭代法

spearhead_cai發表於2019-01-16

第三節課程,介紹的是迭代法。

前兩節筆記的文章:


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。

具體步驟如下圖:

程式設計師的數學筆記3--迭代法

這裡用程式碼實現,如下圖所示:

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
複製程式碼

這裡程式碼中,設定了兩個控制迭代結束的引數:

  1. threshold:誤差的閾值,用於控制解的精度。理論上二分法可以通過無限次迭代求到精確解,但實際應用還需要考慮時間和計算資源,所以一般我們只需要一個近似解,而不需要完全精確的資料;
  2. max_try:控制迭代的次數。設定這個引數也是為了避免使用while True迴圈可能導致的死迴圈,當然理論上設定了threshold是可以避免死迴圈的,但這是一個良好的程式設計習慣,主動避免產生的可能性。
查詢匹配記錄

二分法通過迭代式逼近,不僅可以求得方程的近似解,還可以幫助查詢匹配的記錄

這裡老師給的例子是在自然語言處理中,處理同義詞或者近義詞的擴充套件問題。這時,你是會有一個詞典,用於記錄每個單詞的同義詞或者近義詞。對於一個待查詢單詞,我們需要在字典找到這個單詞,以及對應的所有同義詞和近義詞,然後進行擴充,例如對於單詞--西紅柿,它的同義詞包括了番茄tomato

詞典如下表格所示:

詞條 同義詞1 同義詞2 同義詞3
西紅柿 番茄 tomato ...
... ... ... ...

當處理文章的時候,遇到“西紅柿”這個單詞,就在字典裡查詢,返回“番茄”和“tomato"等同義詞或者近義詞,並新增到文章作為同義詞/近義詞的擴充。

這裡要解決的問題就是如何在字典查詢匹配單詞的問題。一種做法就是雜湊表。而如果不用雜湊表的方法,還可以採用二分查詢法。二分查詢法進行字典查詢的思路如下:

  1. 對整個字典先進行排序(假設是從小到大)。二分法的一個關鍵前提條件就是所查詢區間必須是有序的,這樣每次折半的時候,可以知道是往左還是右繼續查詢。
  2. 使用二分法逐步定位到被查詢的單詞。同樣是每次都選擇查詢區間的中間值,判斷是否和待查詢單詞一致,如果一致就返回;如果不一致,就進行判斷大小,如果比待查詢單詞小,就需要往中間值右邊區間查詢;否則就在左邊區間查詢。
  3. 重複第二步操作,迭代式查詢,直到找到單詞,或者沒有找到,就返回不存在。

相比於利用二分法查詢方程解,二分查詢必須要求資料是有序的!

用程式碼實現如下:

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
複製程式碼

迭代法的介紹就到這裡了!上述原始碼地址:

github.com/ccc013/Code…


歡迎關注我的微信公眾號--機器學習與計算機視覺,或者掃描下方的二維碼,大家一起交流,學習和進步!

程式設計師的數學筆記3--迭代法

相關文章