梯度下降算法_梯度下降算法python实现及其应用

一、python简单实现梯度下降算法       

     假设这样一个场景:一个人被困在山上,需要从山上下来(找到山的最低点,也就是山谷)。但此时山上的浓雾很大,导致可视度很低;因此,下山的路径就无法确定,必须利用自己周围的信息一步一步地找到下山的路。这个时候,便可利用梯度下降算法来帮助自己下山。怎么做呢,首先以他当前的所处的位置为基准,寻找这个位置最陡峭的地方,然后朝着下降方向走一步,然后又继续以当前位置为基准,再找最陡峭的地方,再走直到最后到达最低处;同理上山也是如此,只是这时候就变成梯度上升算法了。

       梯度下降法的基本思想就可以类比为一个下山的过程。首先将特征值映射到实际值从而得到我们的预测函数为

5da3585cf7a67d1b631c53d0add8e58f.png

      其次不断迭代更新该函数的m和c,从而最小化该函数的目标函数,就像沿着山的最陡的方向走下山,到达谷底。

代码如下,我们先假设32da5c617bcff15a80dc048fa1f53ad4.png以及668bf2d25f7b33542182ab115a6c26fc.png,从而建立了一个5b8830f62684d0b4064b860aaaa436fa.png的函数,其中x的取值范围为[-3,3]。

import numpy as npimport matplotlib.pyplot as pltfrom IPython import displayimport time%matplotlib inlinenp.random.seed(seed=1001)x = np.linspace(-2,2, 4)m_true = 1.4c_true = -3.1y = m_true*x+c_trueplt.plot(x, y, 'r.', markersize=10) # plot data as red dotsplt.xlim([-3, 3])

从而得到

ed2229e38c766f9966f2c085fd8cfd60.png

但由于这些点都在一条直线上,这是非常不符合实际情况的,所以我们需要用一些高斯噪声去破坏他们,从而得到下图。

np.random.seed(seed=22050)noise = np.random.normal(scale=0.5, size=4) # standard deviation of the noise is 0.5y = m_true*x + c_true + noiseplt.plot(x, y, 'r.', markersize=10)plt.xlim([-3, 3])

b9cc2c9b23fddb9fa8a2b95bfe8e5436.png

       之后我们建立了100*100的网格去可视化误差函数,并可以绘出误差值等高线图。其中等高线代表着误差值,而横纵坐标分别表示这着网格中m和c的值。

# create an array of linearly separated values around m_truem_vals = np.linspace(m_true-3, m_true+3, 100)# create an array of linearly separated values aec_vals = np.linspace(c_true-3, c_true+3, 100) m_grid, c_grid = np.meshgrid(m_vals, c_vals) E_grid = np.zeros((100, 100))for i in range(100):        for j in range(100):                E_grid[i, j] = ((y - m_grid[i, j]*x - c_grid[i, j])**2).sum()        def regression_contour(f, ax, m_vals, c_vals, E_grid):    "Regression contour plot."    hcont = ax.contour(m_vals, c_vals, E_grid,                       levels=[0, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 64])  # this makes the contour plot            plt.clabel(hcont, inline=1, fontsize=10)  # this labels the contours.        ax.set_xlabel('$m$', fontsize=20)    ax.set_ylabel('$c$', fontsize=20)     f, ax = plt.subplots(figsize=(5,5))regression_contour(f, ax, m_vals, c_vals, E_grid)

2714edcf1a938b35061a069c2171df90.png

再根据目标函数公式

70e07fdc893fef4b3938f01cd61579ed.png

可以得出偏移梯度

51fc1e2a47ef88eef8ea27626ccf4e50.png

及坡度

ba1d5e8efc6e6dd30d4dc679135a999b.png

随后我们将不断迭代更新优化偏移梯度以及坡度以实现最小化误差值。

def plot_regression_contour_fit(f, ax, x, y, learn_rate = 0.1, iters = 10):    m_star = 0.0    c_star = -5.0        E = np.empty(iters+1)        E[0] = ((y - m_star*x - c_star)**2).sum()        regression_contour(f, ax, m_vals, c_vals, E_grid)        ax.plot(m_star, c_star, 'g*', markersize=10)        plt.pause(1.5)        c_vec = c_star        m_vec = m_star        for i in range(iters):                c_grad = -2*(y-m_star*x - c_star).sum()                m_grad = -2*(x*(y-m_star*x - c_star)).sum()                       #c_ant = c_star        #m_ant = m_star        c_star = c_star - learn_rate*c_grad                m_star = m_star - learn_rate*m_grad                c_vec = np.append(c_vec, c_star)                m_vec = np.append(m_vec, m_star)                display.clear_output(wait=True)                f, ax = plt.subplots(figsize=(7,7))                regression_contour(f, ax, m_vals, c_vals, E_grid)                ax.plot(m_vec[0:m_vec.shape[0]-1], c_vec[0:c_vec.shape[0]-1], 'r*', markersize=10)                ax.plot(m_star, c_star, 'g*', markersize=10)                ax.plot(m_vec, c_vec, 'k')                              E[i+1] = ((y - m_star*x - c_star)**2).sum()                print("Iteration {} Objective function: {:02.4f}.".format(i+1, E[i+1]))                plt.pause(1.2)         return m_star, c_starfax = plt.subplots(figsize=(5,5), dpi=80)m_star, c_star = plot_regression_contour_fit(f, ax, x, y, learn_rate = 0.05, iters=5)

      在代码中,我们迭代更新了m和c5次。最后可以看出误差值在网格中的位置不断由等高线高的位置朝等高线低的位置移动,即由海拔高的地方向海拔低的地方移动。误差值也由最初的4.6438下降到了1.1633。

a45592cb20e4e1e7dd07dd3b5a6df4ab.pngb7469cd6e3a457a21f96d9eee24bc5fa.png1a4555d3b9fc18046dfcc3b135d03433.png

4180f4cb1ee06f5a6c8960912bb7bb21.pnge278ca38b6b37c7a892bdedf2e6a7e56.pngb7041f27ffcd67708a5c2887d3daafa5.png

二、梯度下降算法应用——电影推荐系统

        数据源如下图所示,数据源中包含了userId(用户id),movieId(电影id),rating(评分)和tag(标签)。

752d5ae30443b0e4a0f61f7971ae031e.png

    之后,由于用户数量较多,我们定义了用户样本数量'nUsersInexample'为10,并随机选择'nUsers'和他们对电影的评分,最后利用'my_batch_users'列表创建矩阵Y。矩阵Y中包含了用户id,电影id,原始评分以及原始评分减去平均值的评分,从而在之后的坐标系中,观众对电影的评分可以均匀地分布在坐标系零点的两侧。

nUsersInExample = 10 # The maximum number of Users we're going to analyse at one timeindexes_unique_users = ratings['userId'].unique()n_users = indexes_unique_users.shape[0]indexes_users = np.random.permutation(n_users)my_batch_users = indexes_users[0:nUsersInExample]# We need to make a list of the movies that these users have watchedlist_movies_each_user = [[] for _ in range(nUsersInExample)]list_ratings_each_user = [[] for _ in range(nUsersInExample)]list_movies = []list_ratings = []list_users = []for i in range(1, nUsersInExample):    # Movies        local_list_per_user_movies = ratings['movieId'][ratings['userId'] == my_batch_users[i]].values        list_movies_each_user[i] = local_list_per_user_movies        list_movies = np.append(list_movies,local_list_per_user_movies)        # Ratings                                         local_list_per_user_ratings = ratings['rating'][ratings['userId'] == my_batch_users[i]].values        list_ratings_each_user[i] = local_list_per_user_ratings        list_ratings = np.append(list_ratings, local_list_per_user_ratings)          # Users                                           n_each_user = local_list_per_user_movies.shape[0]    local_rep_user =  my_batch_users[i]*np.ones((1, n_each_user))            list_users = np.append(list_users, local_rep_user)    # Let us first see how many unique movies have been ratedindexes_unique_movies = np.unique(list_movies)n_movies = indexes_unique_movies.shape[0]p_list_ratings = np.concatenate(list_ratings_each_user).ravel()p_list_ratings_original = p_list_ratings.tolist()mean_ratings_train = np.mean(p_list_ratings)p_list_ratings =  p_list_ratings - mean_ratings_train # remove the meanp_list_movies = np.concatenate(list_movies_each_user).ravel().tolist()p_list_users = list_users.tolist()Y = pd.DataFrame({'users': p_list_users, 'movies': p_list_movies, 'ratingsorig': p_list_ratings_original,'ratings':p_list_ratings.tolist()})

       推荐系统旨在根据用户的喜好对商品(电影、书籍及其他商业产品)提出建议。推荐引擎需要考虑到所有用户的口味和每个对象的特性。而组织对象常见的一种方法为将具有相近特征的对象放置在空间的相近位置。所以在电影推荐系统中,用户以及电影名称以散点的形式展现在图像上,并基于梯度下降算法不断优化用户以及电影之间的距离,从而对特定用户推荐电影——相邻特定用户位置最近的电影。

     首先,我们需要为每个用户和电影定义他们在坐标系中的位置。对于一个2D平面,我们需要将用户和电影的位置分别存在两个数字向量中。即

cab1bc3022f9aa4af7dcba9b5cd58442.png

        接下来,我们需要一个度量来确定电影和用户之间的相似性,即向量的内积。向量的内积指的是向量中每个元素的乘积的总和,即

d7907f14517c546f5fd391e47bde407e.png

   电影推荐系统可通过该公式计算电影和用户之间的相似性。用户和电影的相似越高,用户给电影的评分就越高,正好对应了数据中所包含的评分信息。所以我们现需要建立两个包含用户坐标和电影坐标的大向量集,也就是特征向量集

ed56eb7834e828b518788a958c353914.png

从而有目标函数

fe16f87ad066b40bba01473c36eac29a.png

    其中0f6c786adcec8ee8bf6710e9d3f5dd18.png是一个布尔变量,1则是用户为此电影打过分,0则是用户未给此电影打过分。之后可通过梯度下降算法

a8f27f7a13488669dd58239cc3584d55.png

优化目标函数。而

1d7ebe3ffbe418061a8c014eefe7bce8.png

同理可得

d7fe8aefdccaee71e74085ca29278f04.png

    在优化目标函数的过程中,我们首先需要初始化矩阵U和矩阵V。这可以通过pandas中DataFrame利用随机较小的值初始它们的位置。

q = 2 # the dimension of our map of the 'library'learn_rate = 0.01U = pd.DataFrame(np.random.normal(size=(nUsersInExample, q))*0.001, index=my_batch_users)V = pd.DataFrame(np.random.normal(size=(n_movies, q))*0.001, index=indexes_unique_movies)

     所以U和V可以说是两个随机的数据集,为每个用户和电影随机了一个位置出来,让他们不断优化以寻找自己在坐标系中的位置。优化目标函数代码如下,即梯度下降算法

def objective_gradient(Y, U, V):        gU = pd.DataFrame(np.zeros((U.shape)), index=U.index)        gV = pd.DataFrame(np.zeros((V.shape)), index=V.index)        obj = 0.        nrows = Y.shape[0]        for i in range(nrows):                row = Y.iloc[i]                user = row['users']                film = row['movies']                rating = row['ratings']                prediction = np.dot(U.loc[user], V.loc[film]) # vTu                diff = prediction - rating # vTu - y                obj += diff*diff                gU.loc[user] += 2*diff*V.loc[film]                gV.loc[film] += 2*diff*U.loc[user]        return obj, gUgViterations = 20for i in range(iterations):         obj, gU, gV = objective_gradient(Y, U, V)         print("Iteration", i, "Objective function: ", obj)         U -= learn_rate*gU         V -= learn_rate*gV   

4b5b6eb700b1373017308379c21b7be4.png

      可以看出,误差值随着迭代次数增加而逐渐下降。然而将U和V两个坐标集向量全部迭代一遍,即传统的梯度下降算法,运行所花的时间较长且误差值下降速率较慢。所以随机梯度下降算法应运而生,通过U和V两个坐标集中的随机点迭代,可以看到误差值明显的下降

def stochastic_descent(obj, Y, U, V, learn_rate):        Y = Y.iloc[np.random.permutation(len(Y))]          obj = 0.        nrows = Y.shape[0]        for i in range(nrows):                row = Y.iloc[i]                user = row['users']                film = row['movies']                rating = row['ratings']                prediction = np.dot(U.loc[user], V.loc[film])                  diff = prediction - rating                  obj += diff**2                U.loc[user] -= 2*learn_rate*diff*V.loc[film]                V.loc[film] -= 2*learn_rate*diff*U.loc[user]        return obj, U, Vlearn_rate = 0.05iterations = 20obj = 0.for i in range(iterations):        obj, U, V = stochastic_descent(obj, Y, U, V, learn_rate)        print("update {}  objective function: {}".format(i+1, obj))  

0a62aafacd33ab9978778d4623a9b3d8.png

     而从最后显示的2D用户和电影的坐标散点图可以看到电影坐标的散点已经分散,以寻找相性较大的用户坐标。

21f8501ec0dabfb39c46df77ffd0c328.png

      随着迭代次数的增加,坐标图中电影的散点会变得更加分散,并与相性较大的用户的距离变得更近,从而给出用户坐标临近的电影坐标作为电影推荐给用户。

1b051ce11c4987cff61606813d9da22a.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值