冷启动问题可以分为以下三类。
针对用户冷启动,下面给出一些解决方案。
利用用户的账号信息。一般来说,国内腾讯的QQ号、微信号,淘宝的旺旺号,新浪的微博号,国外的 Google 账号、Facebook 账号已经成为大部分APP的快速注册账号。如图4.28所示,网站或APP可以通过公开的SDK支持外部账号的登录。
如果用户使用这些账号进行登录,我们可以通过这些账号信息追溯用户在平台上的行为,作为冷启动的参考。
有Android 系统的手机开放度较高,因此对于各大商家来说多了很多了解用户的机会。iPhone也有类似的接口可以获取到 OpenUDID来区分不同的设备。
比如大家在淘宝浏览了某些物品后,在今日头条、网易新闻等APP的广告推荐中,就立刻有了相关产品的广告,这就是因为他们在背后已经用设备号将你在不同APP中的行为连接起来了。
相对于前面两个方案来说,这种路径不够自然,用户体验相对较差,但是如果给予足够好的设计,还是能吸引用户去选择自己感兴趣的点,而使转化率得以提升。比如在QQ 音乐APP中就有用户偏好的选择页面,如下图所示,网易云音乐也有类似的功能入口。
物品冷启动需要解决的问题是如何将新加入的物品推荐给对它感兴趣的用户。这时候最基本的方法是通过物品描述等文字中的语义来计算其相似度。常用的算法有TF—IDF。
2009年Amatriain等人发表在ACM的一篇关于推荐系统的文章《The wisdom of thefow: a collaborative filtering approach based on expert opimions from the web》。所谓少数人的智慧,实际指的是作者提出的基于专家的协同过滤(CF)在某些方面要优胜于传统的CF算法。在某些场景下,基于专家标注的数据效果甚至好于基于用户行为的数据。
例如国外的Pandora音乐APP中,描述一首歌曲的特征细化到了歌曲的编曲、乐器搭配、乐器演奏特征、风格、根源、人声的特征、曲调、旋律特征等维度,并且以一种非常客观的角度来描述歌曲的特征,是一种所有人耳朵都能接触到的物理属性,即不会随欣赏者阅历的不同而有不同的认知,其中排除了情感属性。而且Pandora能显示出来的这些标签仅仅是音乐基因非常小的一部分,还有很多其他没有曝光的音乐标签,但也足以窥见这是种专业而客观的描述方式,把一首歌当作一个看得见摸得着的物品进行剖析,用标签来描述它。下面介绍如何通过专家数据进行推荐。
首先定义专家:在一个特定的领域内,他们能对该领域内的条目给出深思熟虑的、一致的、可靠的评价(打分)。在《The wisdom of the few》这篇文章里,作者并没有详细地探讨如何从数据中发现一批领域专家,他们挑选的是一批从http://rottentomatoes.com 爬取的现成的电影评论专家,这样可以使得他们讨论的主题更为集中,因为这些专家都是经过人工筛选的,所以,可以忽略因专家挑选算法的不足而给后续算法与分析带来的偏差。
在音乐推荐中,音乐流派是相当重要的特征,音乐的数据库是庞大的,像QQ音乐、网易云音乐等平台,音乐的量级都在千万以上。如果分类一首歌曲需要最快花费3秒来计算,人工对1000万首歌分类至少需 要833小时,且这是理想状态之下。所以我们就在想能否通过使用深度学习来帮助我们完成这项劳动密集型任务。
首先,我们需要一个数据集,为此我们需要一个已知流派分类的样本库。在QQ音乐、豆瓣、网易云音乐平台上均有这样的流派分类,分类里面的歌曲虽然不全,但是已足够训练模型了。下面的案例主要选择了摇滚、民谣、爵士、电子四个流派,每个流派下载了1000首歌。
一旦我们有足够多的流派,并有足够的歌曲,我们就可以开始从数据中提取重要信息。一首歌对应一个音频文件。经典的采样频率为44100Hz—每秒音频存储44100个值,而立体声则为两倍。这意味着一首3分钟长的立体声歌曲包含7938000个样本。这样训练量会非常大,我们可以先把立体声声道丢弃,因为它包含高度冗余的信息。
接下来使用傅里叶变换将我们的音频数据转换到频域。这使得数据的表示更加简单和紧凑,我们将其以谱图形式输出。这个过程会给我们生成一个PNG文件,其中包含我们的歌曲的所有频率随着时间的变化。这个步骤可以借助libsora工具来完成。我们使用 每秒50像素(每像素20ms),以降低PNG图片的分辨率并将图片切割成10~15s的片段,因为一般来说10s就足够用于判断音乐的分类了。最后就可以得到如图4.39这样的频谱图。
时域位于x轴上,频率位于y轴上,最高频率在顶部,最低频率在底部。频率的缩放幅度以灰度显示,其中白色是最大值,黑色是最小值。这样我们就把音频分类问题,转化为图片分类问题。

构建如图4.40所示的CNN网络分类模型。
利用上面的CNN分类模型,我们已经可以得到一个不错的音乐分类模型了。然而,CNN模型却不完全适用于音频分类。一般图片分类具有invariance(不变性),即图片旋转后对分类不会有影响,但是音频的频谱图并不是这样,它在x轴和y轴分别表示具体时域和频域的特征。另外CNN通过 filter size 获取前后信息,但是受限于size大小,longdependence方面不如LSTM。所以进一步提出了CNN+LSTM的音乐分类结构

模型是在二层卷积层后,把不同通道上相同时序上的特征组合起来,作为LSTM层的输入,然后再通过全连接层提取出进一步的音频分类特征。经过测试,方案-CNN分类模型的四分类准确率为67%,而方案二的准确率可以达到73%,对最后的分类准确率提升比较明显。
我们观察分类结果的混淆矩阵,其中纵坐标表示测试集音乐标注好的流派分类,横坐标为模型预测的分类。当纵坐标和横坐标标签一致时,说明模型预测正确;不一致时,说明模型预测有偏差。
除了流派分类结果可作为标签特征应用到模型,模型倒数第二层的128维向量也可以作为歌曲特征应用到模型里。事实上,使用高维度的向量特征,比流派分类这种低维特征信息表达能力强,对模型的效果提升更加明显。
模型结构设计代码如下
- from keras.models import Sequential
- from keras.layers import Dense, Activation, Dropout, Flatten,Conv2D,MaxPooling2D,ZeroPadding2D
- from keras.layers.recurrent import LSTM,GRU
- from keras.layers.core import Dense, Activation, Dropout, Flatten,Reshape,Permute
- import cv2
- import os
- import csv
- import keras
- from config import timeratio
-
- def createModel(nbClasses,imageSize):
- model = Sequential()
- model.add(Conv2D(filters=256,input_shape = (imageSize * time,imageSize,1),kernel_size= (3,3),activation='relu',padding = 'same'))
- model.add(MaxPooling2D(pool_size = (2,2)))
- model.add(Dropout(rate=0.5))
-
- model.add(Conv2D(filters=512,input_shape = (192,64,526),kernel_size= (3,3),activation='relu',padding = 'same'))
- model.add(MaxPooling2D(pool_size = (2,2)))
- model.add(Dropout(rate=0.5))
-
- model.add(Flatten())
- model.add(Permute((1,3,2),input_shape(382,1,64)))
- l1=96
- l2=32*512
-
- model.add(Reshape((11,12)))
-
- model.add(LSTM(batch_input_shape = (None,11,12),
- output_dim = 512,
- unroll = True))
-
- model.add(Dense(units=1024,activation = 'relu'))
- model.add(Dropout(rate=0.5))
-
- model.add(Dense(units=256,activation = 'softmax'))
- model.add(Dropout(rate=0.5))
-
- struct = model,summary()
-
- print(struct)
-
- model.compile(loss = 'catagorical_crossentropy',optimizer = 'adam',metrics=['accuracy'])
-
- return model
CNN网络对人脸进行端对端的打分,不仅省去大量特征构造与提取的工作,准确率也得到较大提升,因为审美存在较大差异(欧美和亚洲),所以对于魅力值这样的主观指标,使用合适的数据集至关重要。为了提升分类准确率,减少背景和脸部位置、大小的干扰,可以先使用一些人脸检测的开源工具进行脸部的位置截取(归一化)
- from keras.models import Sequential
- from keras.layers import Dense, Activation, Dropout, Flatten,Conv2D,MaxPooling2D,ZeroPadding2D
- import cv2
- import os
- import csv
- import keras
- import numpy as np
- import matplotlib.pyplot as plt
-
- def shape_of_array(arr):
- array = np.array(arr)
- return array.shape
-
- #获得样本评分,并归一化
- def get_label(num):
- with open('./label.csv') as csvfile:
- reader = csv.DictReader(csvfile)
- for row in reader:
- if row['#Image']== str(num):
- return float((row['Attractiveness label']))
-
- #加载训练集图片样本
- def load_image_data(filedir):
- label = []
- image_data_list = []
- train_image_list = os.listdir(filedir)#返回指定的文件夹包含的文件或文件夹的名字的列表。
-
- for img in train_image_list:
- #os.path.join()函数用于拼接文件路径,可以传入多个路径
- url = os.path.join(filedir + img)
- image = cv2.imread(url)
- image = cv2.resize(image,(128,128))#重塑图片尺寸
- image_data_list.append(image)
-
- img_num = int(img[img.find('P-')+2 :img.find('.')])
- att_label = get_label(img_num)/5.0
- print(img_num,' ',att_label)
- label.append(att_label)
-
- img_data = np.array(image_data_list)
- img_data = img_data.astype('float32')
- img_data /= 255
- return img_data,label
-
-
- #网络结构
- def make_network():
-
- model = Sequential()
- model.add(Conv2D(filters=32,input_shape = ( 128,128,3),kernel_size= (3,3),activation='relu',padding = 'same'))
- model.add(Conv2D(filters=32,kernel_size= (3,3),activation='relu',padding = 'same'))
-
- model.add(MaxPooling2D(pool_size = (2,2)))
- model.add(Dropout(rate=0.5))
-
- model.add(Flatten())
- model.add(Dense(units=128,activation = 'relu'))
- model.add(Dropout(rate=0.5))
- model.add(Dense(units=1,activation = 'tanh'))
-
-
- return model
-
- #模型训练主函数
- def main():
- train_x,train_y = load_image_data('/Data_Collection_face_resize')
- model = make_network()
- model.summary()
- model.compile(loss = 'mean_squared_error',optimizer = 'adam',metrics=['mae'])
- history = model.fit(train_x,train_y,batch_size = 100,validation_split = 0.3,epochs = 300,verbose=1)
-
- model.evaluate(train_x,train_y)
- model.save('face.h5')
-
- plt.plot(history.history['loss'])
- plt,plot(history.history['val_loss'])
- plt.title('train history')
- plt.xlabel('acc')
- plt.ylabel('epoch')
- plt.legend(['train,''validation',loc = 'upper left'])
- plt.show()
-
- if __name__ == '__main__':
- main()
相较于传统的SVR机器学习,CNN模型对魅力值的打分准确率有了显著提升。但网络月神,梯度消失的现象就越明显,网络的训练效果也不会很好,用残差网络可以改善梯度消失。
当数据量较小,深度学习可能会出现过拟合现象,但想运用VGGNet,GoogleNet等预训练模型,可以只对网络最后面的几层进行重新训练,对于神经网络的底层,它充分在大数据集上进行了基础特征的提取(颜色,边框),依然可以在我们的数据集上进行运用,需要经过微调
- keras.applications.resnet50.ResNet50(include_top=True,
- weights='imagenet',
- input_tensor=None,input_shape=None,
- pooling=None,
- classes=1000)
上面API中的50层残差网络模型,权重训练自 ImageNet。参数含义如下:
- keras.applications.resnet50.ResNet50(include_top=True,
- weights='imagenet',
- input_tensor=None,input_shape=None,
- pooling=None,
- classes=1000)
-
-
-
- from__future__ import print function import keras as K
-
- from keras.models import Sequential,Model
- from keras.layers.core import Dense,Dropout,Flatten,Activation
- from keras.layers import Dense,Dropout,Activation,Flatten,merge,Input, concatenate
- from keras.layers,convolutional import Conv2D,MaxPooling2D from keras.applications import ResNet50
- import cv2 import os
- import numpy as np import csv
- import matplotlib.pyplot as plt
-
- def shape_of_array(arr):
- array = np.array(arr)
- return array.shape
-
- #获得样本评分,并归一化
- def get_label(num):
- with open('./label.csv') as csvfile:
- reader = csv.DictReader(csvfile)
- for row in reader:
- if row['#Image']== str(num):
- return float((row['Attractiveness label']))
-
- #加载训练集图片样本
- def load_image_data(filedir):
- label = []
- image_data_list = []
- train_image_list = os.listdir(filedir)#返回指定的文件夹包含的文件或文件夹的名字的列表。
-
- for img in train_image_list:
- #os.path.join()函数用于拼接文件路径,可以传入多个路径
- url = os.path.join(filedir + img)
- image = cv2.imread(url)
- image = cv2.resize(image,(128,128))#重塑图片尺寸
- image_data_list.append(image)
-
- img_num = int(img[img.find('P-')+2 :img.find('.')])
- att_label = get_label(img_num)/5.0
- print(img_num,' ',att_label)
- label.append(att_label)
-
- img_data = np.array(image_data_list)
- img_data = img_data.astype('float32')
- img_data /= 255
- return img_data,label
-
-
- #模型训练主函数
- def main():
- train_x,train_y = load_image_data('/Data_Collection_face_resize')
- input_tensor = Input(shape = (200,200,3,))
- base_model = ResNet50(include_top = False,weights = 'imagenet')(input_tensor)
- flat = Flatten()(base_model)
- dense128 = Dense(128,activation = 'relu',kernel_initializer = 'normal',name = 'dense128')(flat)
- output = Dense(1,name = 'output')(dense128)
- model = Model(inputs = [input_tensor],outputs = [output])
- print(model.summary())
-
-
- model.compile(loss = 'mean_squared_error',optimizer = 'adam',metrics=['mae'])
- history = model.fit(train_x,train_y,batch_size = 100,validation_split = 0.3,epochs = 300,verbose=1)
-
- model.evaluate(train_x,train_y)
- model.save('resnet50.h5')
-
- plt.plot(history.history['loss'])
- plt,plot(history.history['val_loss'])
- plt.title('train history')
- plt.xlabel('acc')
- plt.ylabel('epoch')
- plt.legend(['train,''validation',loc = 'upper left'])
- plt.show()
-
- if __name__ == '__main__':
- main()
使用ResNet50网络进行finetuning后,模型训练的误差可以有显著的降低,图片分类问题,在多数情况下,基于已经训练好的网络进行微调(finetuning),都能显著提高效率和效果。Keras中包含了大部分效果比较好的图像分类模型