发布时间:2020/07/19 作者:天马行空 阅读(1163)
这一关里,我们的任务就是找出其中适合自己的岗位。利用简单的人工智能算法完成一个职位推荐系统,这个小系统可以根据使用者输入的基本资料为使用者提供一些合适的岗位。
一、筛选
硬性条件:主要包括学历和工作经验
弹性条件:城市和技能、薪资
1.1、工作经验
import pandas as pd def work_year(data, years): try: # 按工作经验筛选行 selected = data[data['工作经验'] <= int(years)] return selected except ValueError: print('请输入一个阿拉伯数字整数,无工作经验则输入0。') if __name__ == '__main__': # 读取文件 data = pd.read_excel('data.xlsx') # 输入工作经验 years = input('工作经验(年):') selected = work_year(data, years) # 输出结果 for i in range(len(selected)): print("******第{}个合适的职位******".format(i+1)) print("职位名称: " + (selected.loc[:, '职位名称'].iloc[i])) print("公司简称: " + (selected.loc[:, '公司简称'].iloc[i])) print("薪资: {}-{}元" .format(selected.loc[:, '薪资下限'].iloc[i], selected.loc[:, '薪资上限'].iloc[i]))
1.2、学历要求
学历属性是一种序数属性,它的属性值存在自然排序。
序数属性(ordinal attribute)是一种属性,其可能的属性值之间具有有意义的排序的属性,但是这些值之间的差是无法度量的。就像学历属性,我们知道硕士比本科要好,但是具体好多少却是不能度量的。
虽然学历属性的四种取值可以排序,但是因为学历属性对应的属性值不是数字而是字符串,我们没有办法让程序通过比大小的方式来直接判断和筛选。
对于输入问题,我们可以通过提供选项的方式来进行规范,就像让用户做选择题一样。这样的形式也方便了我们进行代码实现,只要将选项字母保存为一个列表,通过判断用户输入是否存在于列表中,如果不在列表中,则提醒用户输入错误。
解决了输入的问题,就要来思考进行筛选的方法。既然学历在现实中是存在有意义的排序的,可以在data中根据学历取值添加一个属性值为数字的列,来方便进行比较。也就是说,可以将字符串类型的四种学历 "硕士" 、"本科"、"大专"、"不限",替换为四个对应的学历等级3、2、1、0,也就是越大的数字就对应越高的学历等级。
import pandas as pd data = pd.read_excel('data.xlsx') # 创建一个列表用来学历等级属性保存对应的列值 edu_level = [] # 赋值 for edu_text in data.loc[:, '学历要求']: if edu_text == '硕士': edu_level.append(3) elif edu_text == '本科': edu_level.append(2) elif edu_text == '大专': edu_level.append(1) else: edu_level.append(0) # 添加新的列 data['学历等级'] = edu_level print(data[['学历要求', '学历等级']])
二、匹配
2.1、工作城市
城市属性是一个标称属性。标称属性(nominal attribute)的值是一些符号或实物的名称,可以说标称属性是分类型的属性(categorical),这种属性的取值不是定量的,而且不能排序。
城市属性的值以文本的形式存在,并且不同的城市之间并不存在有意义的比较或者排序。而且,对于城市这个属性而言,符合用户预期的城市通常不只有一个。
所以在输入这个环节可以要求用户用空格来分隔不同的城市。这样一来,我们就可以用字符串的split()方法把用户输入的城市处理为一个候选列表。
这样在进行匹配时,可以使用成员算符(not in或in)来判断数据中的每个职位的城市属性值是否在用户提供的候选城市列表里,如果不在就用drop()方法将对应的该行数据从候选结果中去除。
import pandas as pd def work_city(city, data): # 将输入的候选城市字符串转换成列表类型,存到city中。 city = city.split() # 拷贝一份数据,存到selected变量中,作为候选结果 selected = data.copy() # 按城市筛选行 for i in range(len(data)): # 补充以下一行代码,需要添加一条if判断语句, # 判断data第i行的城市值是否在用户输入的city列表中, # 如果不在,需要执行selected = selected.drop([i])把这行从候选结果中去除。 if data.loc[i, '城市'] not in city: selected = selected.drop([i]) return selected if __name__ == "__main__": # 读取文件 data = pd.read_excel('data.xlsx') # 输入城市 city = input('城市(空格分隔):') # 筛选和输出 result = work_city(city, data) if len(result) == 0: print("没有找到符合预期的职位。") else: for i in range(len(result)): print("******第{}个合适的职位******".format(i+1)) print("职位名称: " + (result.loc[:, '职位名称'].iloc[i])) print("公司简称: " + (result.loc[:, '公司简称'].iloc[i])) print("薪资: {}-{}元".format(result.loc[:, '薪资下限'].iloc[i], result.loc[:, '薪资上限'].iloc[i]))
2.2、技能标签
和城市相似,技能同样是标称属性,而且用户输入的技能也可能不止一个。
不同的是,技能标签有很多缺失值(有些职位需求中没有技能标签)。另外,有的技能标签的值有多个,这些值是用逗号分隔的。
首先,可以确定的是,对于没有技能要求的职位,都可以算是“合适”的职位。
其次,而对于有技能要求的职位,则需要比较用户的技能和职位要求的技能之间的相似程度。
最后,与工作经验、学历要求和城市这些属性不同,技能的筛选不是“非此即彼”的,可以根据职位要求和用户输入信息的相似程度来进行排序。
在数学中,对于有限的集合,我们可以使用杰卡德系数(一定不要忘记,人工智能的基础是数学)来表示两个集合的相似度。这个系数的值是两个集合的交集大小比并集大小。杰卡德系数值越大,两个集合的相似度程度越高。
首先使用split( )方法将用户输入的技能信息和从数据表中读取的信息都转换为列表,再使用set( )函数转换为集合。
而对于集合数据类型我们可以通过运算符&和|分别进行交集和并集的计算。
set1 = {'A', 'B', 'C', 'D', 'E'} set2 = {'A', 'E', 'I', 'O', 'U'} # 求交集的方法 and_set = set1 & set2 print("两个集合的交集是: {}".format(and_set)) # 求并集的方法 or_set = set1 | set2 print("两个集合的并集是: {}".format(or_set))
技能标签匹配
import pandas as pd def skill_label(skill, data): # 将输入用户输入的查询词都转换为大写,使用split()方法将输入切成列表 # 再转换成集合 skill_user = set(skill.upper().split()) # 插入杰卡德系数列 jaccard = [] # 遍历数据中的'技能标签'列 for skill_text in data.loc[:, '技能标签']: # 假设没有技术要求的职位杰卡德系数为0.01(排在推荐列表最末) if pd.isnull(skill_text): jaccard.append(0.01) # 计算有技术要求的职位的杰卡德系数 else: # 技能标签内容先转为大写,然后已逗号为分隔符切分成列表 # 再转成集合 skill_company = set(str(skill_text).upper().split(',')) and_size = len(skill_company & skill_user) #交集 or_size = len(skill_company | skill_user) #并集 jaccard.append(and_size/or_size) #求系数,并追加到jaccard列表中 # 将计算出来的结果添加为data的一列,与原有数据合并 data['杰卡德系数'] = jaccard selected = data.copy() # 去掉杰卡德系数为零的行 for i in range(len(data)): if data.loc[i, '杰卡德系数'] ==0: selected = selected.drop([i]) # 去掉杰卡德系数为零的数据后,将剩余的职位按系数进行降序排列得到返回结果 ordered = selected.sort_values(by='杰卡德系数', ascending=False) return ordered if __name__ == "__main__": # 读取文件 data = pd.read_excel('data.xlsx') # 输入技能 skill = input('技能(空格分隔):\n') # 筛选和输出 result = skill_label(skill, data) if len(result) == 0: print("没有找到符合预期的职位。") else: for i in range(len(result)): print("******第{}个合适的职位******".format(i+1)) print("职位名称: " + (result.loc[:, '职位名称'].iloc[i])) print("公司简称: " + (result.loc[:, '公司简称'].iloc[i])) print("技能标签:" + str(result.loc[:, '技能标签'].iloc[i])) print("薪资: {}-{}元" .format(result.loc[:, '薪资下限'].iloc[i], result.loc[:, '薪资上限'].iloc[i]))
三、人工智能体验
3.1、整合功能
练习:工作经验+学历要求
import pandas as pd def work_year(years, data): # 按工作经验筛选行 if years == 'A': selected = data[data['工作经验'] < 1] return selected elif years == 'B': selected = data[(data['工作经验'] >= 1) & (data['工作经验']<3)] return selected elif years == 'C': selected = data[(data['工作经验'] >= 3) & (data['工作经验']<5)] return selected elif years == 'D': selected = data[data['工作经验'] >=5] return selected else: print("对不起,你输入的工作经验选项不存在") return None def education(edu, data): options = ['A', 'B', 'C', 'D', 'E'] # 判断用户是否按要求输入了选项对应的大写字母 if edu in options: # 给用户输入值确定一个等级 edu_check = options.index(edu) # 插入学历等级列 edu_level = [] for edu_text in data['学历要求']: if edu_text == '硕士': edu_level.append(3) elif edu_text == '本科': edu_level.append(2) elif edu_text == '大专': edu_level.append(1) else: edu_level.append(0) data['学历等级'] = edu_level # 按学历要求等级筛选行 selected = data[data['学历等级'] <= edu_check] return selected else: print("对不起,你输入的学历选项不存在") return None if __name__ == "__main__": # 读取文件 data = pd.read_excel('data.xlsx') # 经验筛选 years = input('工作经验:\nA. 不足1年\nB. 1-3年\nC. 3-5年\nD. 5年或5年以上\n') # 筛选工作年限 result1 = work_year(years, data) if type(result1) != type(None): # 学历筛选 edu = input('学历(输入选项):\nA. 专科以下\nB. 大专\nC. 本科\nD. 硕士\nE. 硕士以上\n') # 因为我们不想影响原有的DataFrame,所以使用result1.copy表示result1的一个拷贝 result2 = education(edu, result1.copy()) print(result2) if type(result2) != type(None) : if len(result2) == 0: print("没有找到合适的职位。") else: for i in range(len(result2)): print("******第{}个合适的职位******" .format(i+1)) print("职位名称: " + (result2.loc[:, '职位名称'].iloc[i])) print("公司简称: " + (result2.loc[:, '公司简称'].iloc[i])) print("薪资: {}-{}元" .format(result2.loc[:, '薪资下限'].iloc[i], result2.loc[:, '薪资上限'].iloc[i])) else: print('学历信息请输入选项对应的大写字母。') else: print('工作经验请输入选项对应的大写字母。')
假如你尝试输出筛选后的完整结果(result2),你会发现在筛选完工作经验和学历要求之后,result2的索引会变得不连续。
剩下的两个函数,都要求索引是连续的。不然在for循环进行 drop 操作的时候,会出现索引值没有办法匹配的情况。
因此,在把剩下的函数整合到程序里之前,我们还需要一个整理索引值的小函数。
pandas的DataFrame类为我们提供了一个方法:reset_index(drop=True, inplace=True)。
当调用这个方法的时候,传入如这里所示的drop和inplace两个参数,表示在去除原有索引的情况下,新建从零开始的连续索引,并且所有修改直接在当前的数据上起作用。
练习:索引重排
import pandas as pd # 测试数据 test = {'身高': [173, 166, 156, 155, 183, 167], '体重': [70, 51, 49, 53, 87, 47]} test_df = pd.DataFrame(test) print("************") print("原数据:") print(test_df) selected = test_df[test_df['身高'] <= 170] print("************") print("筛选后的数据:") print(selected) print("************") print("对筛选后的数据进行重新索引:") selected.reset_index(inplace=True, drop=True) print(selected) 练习:经验+学历+城市+技能 import pandas as pd def work_year(years, data): # 按工作经验筛选行 if years == 'A': selected = data[data['工作经验'] < 1] return selected elif years == 'B': selected = data[(data['工作经验'] >= 1) & (data['工作经验']<3)] return selected elif years == 'C': selected = data[(data['工作经验'] >= 3) & (data['工作经验']<5)] return selected elif years == 'D': selected = data[data['工作经验'] >=5] return selected else: print("对不起,你输入的工作经验选项不存在") return None def education(edu, data): options = ['A', 'B', 'C', 'D', 'E'] # 判断用户是否按要求输入了选项对应的大写字母 if edu in options: # 给用户输入值确定一个等级 edu_check = options.index(edu) # 插入学历等级列 edu_level = [] for edu_text in data['学历要求']: if edu_text == '硕士': edu_level.append(3) elif edu_text == '本科': edu_level.append(2) elif edu_text == '大专': edu_level.append(1) else: edu_level.append(0) data['学历等级'] = edu_level # 按学历要求等级筛选行 selected = data[data['学历等级'] <= edu_check] return selected else: print("对不起,你输入的学历选项不存在") return None def work_city(city, data): city = city.split() selected = data.copy() # 按城市筛选行 for i in range(len(data)): # 把不符合条件的行去掉 if data.loc[i, '城市'] not in city: selected = selected.drop([i]) return selected def skill_label(skill, data): skill_user = set(skill.upper().split()) # 插入杰卡德系数列 jaccard = [] for skill_text in data.loc[:,'技能标签']: # 假设没有技术要求的职位杰卡德系数为0.01(排在推荐列表最末) if pd.isnull(skill_text): jaccard.append(0.01) # 计算有技术要求的职位的杰卡德系数 else: skill_company = set(str(skill_text).upper().split(',')) and_size = len(skill_company & skill_user) #交集 or_size = len(skill_company | skill_user) #并集 jaccard.append(and_size/or_size) #求系数 data['杰卡德系数'] = jaccard selected = data.copy() # 去掉杰卡德系数为零的行 for i in range(len(data)): if data.loc[i, '杰卡德系数'] == 0: selected = selected.drop([i]) # 排序 ordered = selected.sort_values(by='杰卡德系数', ascending=False) return ordered if __name__ == "__main__": # 读取文件 data = pd.read_excel('data.xlsx') # 经验筛选 years = input('工作经验:\nA. 不足1年\nB. 1-3年\nC. 3-5年\nD. 5年或5年以上\n') result1 = work_year(years, data) if type(result1) != type(None): # 学历筛选 edu = input('学历(输入选项):\nA. 专科以下\nB. 大专\nC. 本科\nD. 硕士\nE. 硕士以上\n') # 因为我们不想影响原有的dataframe所以此处传参传入result1的拷贝 result2 = education(edu, result1.copy()) if type(result2) != type(None) : if len(result2) == 0: print("没有找到合适的职位。") else: city = input('城市(空格分隔):\n') result2.reset_index(inplace=True) if len(city.split()) == 0: result3 = result2 else: result3 = work_city(city, result2) if len(result3) == 0: print("没有找到合适的职位。") else: # 技能排序 skill = input('技能(空格分隔):\n') result3.reset_index(inplace=True) if len(skill.split()) == 0: result4 = result3 else: result4 = skill_label(skill, result3) if len(result4) == 0: print("没有找到合适的职位。") else: for i in range(len(result4)): print("******第{}个合适的职位******".format(i+1)) print("职位名称: " + (result4.loc[:, '职位名称'].iloc[i])) print("公司简称: " + (result4.loc[:, '公司简称'].iloc[i])) print("城市: " + str(result4.loc[:, '城市'].iloc[i])) print("工作经验: " + str(result4.loc[:, '工作经验'].iloc[i])) print("学历要求: " + str(result4.loc[:, '学历要求'].iloc[i])) print("技能标签: " + str(result4.loc[:, '技能标签'].iloc[i])) print("薪资: {}-{}元" .format(result4.loc[:, '薪资下限'].iloc[i], result4.loc[:, '薪资上限'].iloc[i])) else: print('学历信息请输入选项对应的大写字母。') else: print('工作经验请输入选项对应的大写字母。')
整个程序的基本功能就是在之前讲过的一个个功能点拼接而成的,在if __name__ == "__main__":这个程序门卫之后,使用一连串的函数调用将基本的筛选操作一一串连起来,并输出最终的计算结果。
3.2、人工智能
在人工智能算法中,我们常常会把文本中涉及的词汇进行转换。把每个词当成一个属性,对于职位需求而言,如果它和这个词有关,我们就把对应属性值标记为1,如果无关则标记为0。这种数据存储方式对于计算机来说就格外友好了。
import pandas as pd import numpy as np from sklearn.metrics.pairwise import cosine_similarity def index_reorder(data): data['index'] = range(len(data)) reordered = data.set_index('index') return reordered # 读取信息 df = pd.read_excel('data_ai.xlsx') # 筛选掉以标签为属性的列 data = df.drop(['职位名称', '薪资下限', '薪资上限', '公司全名', '公司简称', '学历要求', '工作经验'], axis=1) # 将所有标签存为列表,各元素转成大写 labels = list(data.columns.values) labels = [item.upper() for item in labels] # 处理用户的输入,筛选条件全部转成大写,并处理成向量user_values user_keys = input('请输入关键词(空格分隔):\n') user_keys = user_keys.split() user_values = np.zeros(len(labels), int) for key in user_keys: key = key.upper() if key in labels: user_values[labels.index(key)] = 1 user_values = user_values.reshape(-1, len(labels)) # 计算相用户输入与职位特点的似度并排序 similarity_values = cosine_similarity(user_values, data.values) similarity_values = similarity_values.reshape(len(df)) df['相似度'] = similarity_values df = index_reorder(df.sort_values(by=['相似度', '薪资下限', '薪资上限'], ascending=False)) # 输出前十的推荐结果 for i in range(10): print("******推荐岗位No.{}******".format(i+1)) print("职位名称: " + (df.loc[:, '职位名称'].iloc[i])) print("公司简称: " + (df.loc[:, '公司简称'].iloc[i])) print("学历要求: " + (df.loc[:, '学历要求'].iloc[i])) print("工作经验: 至少{}年".format(df.loc[:, '工作经验'].iloc[i])) # print("城市: " + (df.loc[:, '城市'].iloc[i])) print("薪资: {}-{}元" .format(df.loc[:, '薪资下限'].iloc[i], df.loc[:, '薪资上限'].iloc[i]))
总结
序数属性:可以将数据对象通过这类属性值进行排序,但是属性值之间的差异性无法度量的。
二元属性:一种标称属性,只有两个类别或状态:0或1,其中0常表示不出现,1表示出现。如果将0和1对应于False和True,二元属性则为布尔属性。
杰卡德系数:两个集合的交集大小比并集大小,用来表示两个集合的相似度。杰卡德系数的取值范围是从0到1,值越大表示越相似。