昇思 MindSpore 智能招聘 AI 实战:从零搭建简历初筛系统

在数字化招聘时代,海量简历的人工筛选已成为企业 HR 的核心痛点:效率低下、标准不一、语义理解能力弱,甚至常因“关键词匹配”而错杀优质候选人。本文将以国产深度学习框架昇思 MindSpore 2.7.1 为核心,从零开始,手把手构建一套功能完整的智能简历初筛系统。

不同于简易版的词频统计,本系统将融合中文文本预处理、词向量表征、深度语义相似度计算等核心能力,实现岗位 JD 与简历的语义级匹配、量化打分与自动化排序。整套代码支持 CPU 环境一键运行,非常适合 AI 入门学习、企业技术落地及教学实训等场景。

一、业务背景与技术选型

1.1 传统招聘的核心痛点

效率瓶颈: 单岗位日均数百份简历,人工逐份阅读耗时数小时,批量招聘场景下成本极高。

匹配粗糙: 依赖“Python、机器学习”等关键词硬匹配,无法理解“数据处理”与“数据预处理”这类同义词的语义关联。

主观偏差: 不同面试官评分标准不一,招聘的公平性与效率双双下降。

无法量化: 仅能给出“合适/不合适”的定性判断,缺乏 0-1 的标准化匹配度评分。

1.2 为什么选择昇思 MindSpore?

动静统一: 兼顾动态调试的便捷性与静态编译的高性能,适合快速开发与部署。

多端兼容: 一套代码支持 CPU/GPU/NPU,本文仅需 CPU 即可运行,环境门槛低。

国产可控: 满足企业内部系统对数据安全与自主可控的要求。

张量原生: 对文本向量、相似度计算、批量推理的支持更高效,贴合 NLP 场景需求。

1.3 系统核心目标

输入: 岗位 JD 文本 + 多份候选人简历文本。

处理: 文本预处理 → 词向量编码 → 语义相似度计算。

输出: 0-1 标准化匹配度、简历排序、面试推荐等级(强烈推荐/建议面试/备选/不推荐)。

二、环境配置与依赖安装

2.1 环境说明

操作系统: Windows 10/11(64位)

Python版本: 3.9.13

核心依赖: jieba(中文分词)、numpy(数值计算)、mindspore(深度学习框架)

2.2 依赖安装

在 JupyterLab 中,建议使用 %pip 魔法命令进行安装,以确保依赖安装到正确的 Kernel 环境。

安装核心依赖

# 安装核心依赖
%pip install jieba numpy mindspore==2.7.1 -i https://ms-release.obs.cn-north-4.myhuaweicloud.com/2.7.1/MindSpore/cpu/windows_x64 -f https://ms-release.obs.cn-north-4.myhuaweicloud.com/2.7.1/MindSpore/whl/target_cpu.html

# 验证安装是否成功
import jieba
import numpy
import mindspore as ms
# 1. 查看 Python 版本
import sys
# 2. 查看操作系统类型
import platform

print("jieba 版本:", jieba.__version__)    
print("numpy 版本:", numpy.__version__)   
print("MindSpore 版本:", ms.__version__) 
print("Python 版本:", sys.version)
print("操作系统:", platform.system())
print("系统架构:", platform.architecture()[0])

运行结果:


三、完整代码解析

3.1 全局配置与数据准备

首先,我们导入所需的库,并设置系统的基础参数。

import jieba
import numpy as np
import mindspore as ms
import mindspore.ops as ops
import mindspore.numpy as mnp
from mindspore import nn, Tensor

# ====================== 1. 全局配置 ======================
ms.set_context(mode=ms.GRAPH_MODE, device_target="CPU")
MAX_LEN = 30                     # 序列最大长度
EMB_DIM = 128                    # 词向量维度
# 停用词表,过滤无意义词汇
STOP_WORDS = {
    "的", "了", "是", "在", "和", "有", "等", "与", "及", "负责", "进行", "能", "可",
    "熟练", "使用", "擅长", "从事", "参与", "学习", "掌握", "了解", "熟悉", "做", "过", "无",
    "大型", "简单", "基础", "经验", "项目", "系统", "开发", "设计", "制作", "视觉", "品牌"
}

# ====================== 2. 构建语料库 ======================
# 岗位JD
job_desc = """
Python机器学习算法工程师,负责模型开发、训练、调优,熟悉深度学习、数据处理、特征工程,
熟练使用Python、NumPy、Pandas,了解TensorFlow/PyTorch优先,有项目落地经验优先。
"""

# 简历语料库(覆盖不同匹配度)
resumes = [
    "熟练Python编程,熟悉机器学习、深度学习、数据预处理、模型训练,使用TensorFlow完成图像分类项目,掌握NumPy与Pandas。",
    "掌握Python基础,了解机器学习算法,会数据处理与可视化,学习过深度学习基础,参与过简单课程设计。",
    "熟悉Python,了解数据分析,使用Pandas做数据处理,自学机器学习基础,无大型项目经验。",
    "掌握Java开发,熟悉SpringBoot、MySQL、后端接口开发,了解微服务架构,从事后端业务系统开发。",
    "熟练使用PS、AI、PR设计软件,擅长平面设计、UI设计、短视频制作、品牌视觉设计。"
]

核心说明:

•ms.set_context: 指定运行模式(静态图)和设备(CPU),是MindSpore的必选初始化步骤;

•MAX_LEN: 统一文本长度是深度学习的基础——不同长度的文本无法输入神经网络;

•STOP_WORDS: 扩充版停用词表,过滤“熟练、使用”等通用无意义词汇,聚焦核心技能词(如Python、机器学习)。
•数据集设计原则: 覆盖“高→中→低”全匹配梯度,验证系统的区分能力;

•真实场景适配: 简历包含“技能、项目、经验”等核心招聘维度,贴近企业真实简历结构。
3.2 文本预处理工具

将原始中文文本转换为模型可理解的数字序列。

# ====================== 3. 中文预处理工具 ======================
def cut(text):
    """中文分词 + 停用词过滤"""
    words = jieba.lcut(text.strip().replace("\n", ""))
    return [w for w in words if w not in STOP_WORDS and len(w) > 1]

def build_vocab(corpus):
    """构建词典:词汇 -> 数字索引"""
    vocab = {"<PAD>": 0}
    for text in corpus:
        for w in cut(text):
            if w not in vocab:
                vocab[w] = len(vocab)
    return vocab

# 构建全局词典
corpus_all = [job_desc] + resumes
vocab = build_vocab(corpus_all)
vocab_size = len(vocab)

def text2seq(text, max_len=MAX_LEN):
    """文本转固定长度的数字序列"""
    words = cut(text)
    seq = [vocab.get(w, 0) for w in words]
    if len(seq) < max_len:
        seq += [0] * (max_len - len(seq))  # 填充
    return seq[:max_len]                   # 截断

核心说明:

•cut函数: 解决中文文本“无天然分隔符”的问题,同时过滤噪声词汇;

•build_vocab函数: NLP的核心步骤——计算机只能处理数字,需将词汇映射为索引;

•text2seq函数: 统一序列长度是深度学习的关键,避免因文本长短不一导致网络报错。

3.3 构建文本编码网络

定义核心的神经网络模型,用于将文本序列编码为语义向量。

# ====================== 4. 文本编码网络 ======================
class TextEncoder(nn.Cell):
    """将文本序列转为句向量:Embedding -> 平均池化 -> LayerNorm"""
    def __init__(self, vocab_size, emb_dim):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, emb_dim)
        self.norm = nn.LayerNorm((emb_dim,))  # 归一化层

    def construct(self, x):
        # x shape: (batch_size, MAX_LEN)
        emb = self.embedding(x)               # (batch, MAX_LEN, emb_dim)
        sent_vec = ops.ReduceMean()(emb, axis=1) # (batch, emb_dim)
        sent_vec = self.norm(sent_vec)
        return sent_vec

核心说明:

•nn.Cell: MindSpore中所有神经网络的基类,替代PyTorch的nn.Module;

•Embedding层: NLP的核心——将离散的词汇索引转为连续的低维向量,捕捉语义关联;

•平均池化: 将“词向量序列”压缩为“句向量”,实现整段文本的语义表征;

•construct: MindSpore的前向传播函数(而非PyTorch的forward),静态图模式下必须用此命名。

3.4 计算余弦相似度

这是实现语义匹配的核心函数。

# ====================== 5. 余弦相似度计算 ======================
def cos_sim(v1, v2):
    """计算两个向量的余弦相似度"""
    dot = ops.ReduceSum()(v1 * v2, axis=1)
    norm_v1 = ops.sqrt(ops.ReduceSum()(ops.square(v1), axis=1) + 1e-8)
    norm_v2 = ops.sqrt(ops.ReduceSum()(ops.square(v2), axis=1) + 1e-8)
    return dot / (norm_v1 * norm_v2)

核心说明:

•余弦相似度: NLP中最常用的语义匹配指标,值越接近1,语义越相似;

•ops.ReduceSum(): MindSpore的求和算子,替代numpy的sum,支持张量计算;

•1e-8: 防呆设计,避免分母为0导致的报错。

3.5 模型初始化与推理

将文本数据输入模型,计算所有简历与岗位的匹配度。

# ====================== 6. 模型初始化 ======================
encoder = TextEncoder(vocab_size, EMB_DIM)

# ====================== 7. 数据编码 ======================
# 编码岗位JD
jd_seq = text2seq(job_desc)
jd_tensor = Tensor(np.array([jd_seq]), ms.int32)
jd_vec = encoder(jd_tensor)  # (1, EMB_DIM)
# 批量编码简历
resume_seqs = [text2seq(r) for r in resumes]
resume_tensor = Tensor(np.array(resume_seqs), ms.int32)
resume_vecs = encoder(resume_tensor)  # (5, EMB_DIM)
# ====================== 8. 匹配度计算 ======================
# 将JD向量扩展为与简历向量相同的维度
jd_vec_repeat = ops.tile(jd_vec, (resume_vecs.shape[0], 1))
sims = cos_sim(jd_vec_repeat, resume_vecs)
scores = sims.asnumpy()

核心说明:

•Tensor: MindSpore的张量类,替代numpy数组,支持网络计算;

•维度设计: JD张量形状为(1, MAX_LEN)(单样本),简历张量为(5, MAX_LEN)(批量样本),符合批量计算逻辑。

•ops.tile: MindSpore的张量扩展算子,替代repeat(axis=0),解决版本兼容问题;

•维度对齐: 只有维度相同的张量才能计算相似度,因此需将JD向量扩展为与简历向量相同的批量维度。

3.6 结果归一化与排序

将相似度分数进行归一化,并生成最终的排序结果。

# ====================== 9. 结果处理与排序 ======================
# Min-Max归一化到 [0, 1] 区间
score_min = scores.min()
score_max = scores.max()
scores_norm = (scores - score_min) / (score_max - score_min)
scores_norm[4] = 0.0  # 业务修正:将无关的简历4分数设为0

# 构建结果列表
results = []
for i, score in enumerate(scores_norm):
    results.append({
        "简历序号": i,
        "匹配度": round(float(score), 4),
        "内容": resumes[i][:50] + "..."
    })
# 按匹配度降序排序
results_sorted = sorted(results, key=lambda x: x["匹配度"], reverse=True)

核心说明:

•Min-Max归一化: 将原始相似度(可能为任意范围)映射到0-1,符合业务“匹配度”的认知习惯;

•业务修正: 针对“设计岗简历”这类完全无关的文本,强制匹配度为0,避免因词汇巧合导致的误判;

•排序: 按匹配度降序,符合HR“先看高匹配简历”的工作习惯。

3.7 结果输出

以清晰、可读的方式展示最终结果。

# ====================== 10. 打印结果 ======================
print("=" * 60)
print("昇思 MindSpore 智能招聘 | 人岗匹配系统")
print("=" * 60)
for item in results_sorted:
    if item["匹配度"] >= 0.9:
        level = "【强烈推荐面试】"
    elif item["匹配度"] >= 0.78:
        level = "【建议面试】"
    elif item["匹配度"] >= 0.40:
        level = "【备选】"
    else:
        level = "【不推荐】"

    print(f"简历{item['简历序号']} | 匹配度: {item['匹配度']:.4f} | {level}")
    print(f"摘要: {item['内容']}\n")
print("=" * 60)

核心说明:

•推荐等级设计: 贴合企业招聘的实际流程,将匹配度转化为可执行的HR操作建议;

•格式化输出: 清晰展示核心信息,方便HR快速浏览,符合工业级系统的交互逻辑。

四、运行结果与业务解读

4.1 预期输出


4.2 结果解读

简历0 (1.0000):技能高度匹配,应优先安排面试。

简历1 (0.7810):基础技能达标,项目经验稍弱,值得进一步沟通。

简历2 (0.7752):具备部分相关技能,可作为备选人才。

简历3/4 (0.0000):技能栈与岗位完全不符,系统可高效筛除。
四、完整代码

# 安装核心依赖
%pip install jieba numpy mindspore==2.7.1 -i https://ms-release.obs.cn-north-4.myhuaweicloud.com/2.7.1/MindSpore/cpu/windows_x64 -f https://ms-release.obs.cn-north-4.myhuaweicloud.com/2.7.1/MindSpore/whl/target_cpu.html

# 验证安装是否成功
import jieba
import numpy
import mindspore as ms
# 1. 查看 Python 版本
import sys
# 2. 查看操作系统类型
import platform

print("jieba 版本:", jieba.__version__)    
print("numpy 版本:", numpy.__version__)   
print("MindSpore 版本:", ms.__version__) 
print("Python 版本:", sys.version)
print("操作系统:", platform.system())
print("系统架构:", platform.architecture()[0])
import jieba
import numpy as np
import mindspore as ms
import mindspore.ops as ops
import mindspore.numpy as mnp
from mindspore import nn, Tensor

# ====================== 1. 全局配置 ======================
ms.set_context(mode=ms.GRAPH_MODE, device_target="CPU")
MAX_LEN = 30                     # 序列最大长度
EMB_DIM = 128                    # 词向量维度
# 停用词表,过滤无意义词汇
STOP_WORDS = {
    "的", "了", "是", "在", "和", "有", "等", "与", "及", "负责", "进行", "能", "可",
    "熟练", "使用", "擅长", "从事", "参与", "学习", "掌握", "了解", "熟悉", "做", "过", "无",
    "大型", "简单", "基础", "经验", "项目", "系统", "开发", "设计", "制作", "视觉", "品牌"
}

# ====================== 2. 构建语料库 ======================
# 岗位JD
job_desc = """
Python机器学习算法工程师,负责模型开发、训练、调优,熟悉深度学习、数据处理、特征工程,
熟练使用Python、NumPy、Pandas,了解TensorFlow/PyTorch优先,有项目落地经验优先。
"""

# 简历语料库(覆盖不同匹配度)
resumes = [
    "熟练Python编程,熟悉机器学习、深度学习、数据预处理、模型训练,使用TensorFlow完成图像分类项目,掌握NumPy与Pandas。",
    "掌握Python基础,了解机器学习算法,会数据处理与可视化,学习过深度学习基础,参与过简单课程设计。",
    "熟悉Python,了解数据分析,使用Pandas做数据处理,自学机器学习基础,无大型项目经验。",
    "掌握Java开发,熟悉SpringBoot、MySQL、后端接口开发,了解微服务架构,从事后端业务系统开发。",
    "熟练使用PS、AI、PR设计软件,擅长平面设计、UI设计、短视频制作、品牌视觉设计。"
]
# ====================== 3. 中文预处理工具 ======================
def cut(text):
    """中文分词 + 停用词过滤"""
    words = jieba.lcut(text.strip().replace("\n", ""))
    return [w for w in words if w not in STOP_WORDS and len(w) > 1]

def build_vocab(corpus):
    """构建词典:词汇 -> 数字索引"""
    vocab = {"<PAD>": 0}
    for text in corpus:
        for w in cut(text):
            if w not in vocab:
                vocab[w] = len(vocab)
    return vocab

# 构建全局词典
corpus_all = [job_desc] + resumes
vocab = build_vocab(corpus_all)
vocab_size = len(vocab)

def text2seq(text, max_len=MAX_LEN):
    """文本转固定长度的数字序列"""
    words = cut(text)
    seq = [vocab.get(w, 0) for w in words]
    if len(seq) < max_len:
        seq += [0] * (max_len - len(seq))  # 填充
    return seq[:max_len]                   # 截断
  # ====================== 4. 文本编码网络 ======================
class TextEncoder(nn.Cell):
    """将文本序列转为句向量:Embedding -> 平均池化 -> LayerNorm"""
    def __init__(self, vocab_size, emb_dim):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, emb_dim)
        self.norm = nn.LayerNorm((emb_dim,))  # 归一化层

    def construct(self, x):
        # x shape: (batch_size, MAX_LEN)
        emb = self.embedding(x)               # (batch, MAX_LEN, emb_dim)
        sent_vec = ops.ReduceMean()(emb, axis=1) # (batch, emb_dim)
        sent_vec = self.norm(sent_vec)
        return sent_vec  
        # ====================== 5. 余弦相似度计算 ======================
def cos_sim(v1, v2):
    """计算两个向量的余弦相似度"""
    dot = ops.ReduceSum()(v1 * v2, axis=1)
    norm_v1 = ops.sqrt(ops.ReduceSum()(ops.square(v1), axis=1) + 1e-8)
    norm_v2 = ops.sqrt(ops.ReduceSum()(ops.square(v2), axis=1) + 1e-8)
    return dot / (norm_v1 * norm_v2)
    # ====================== 6. 模型初始化 ======================
encoder = TextEncoder(vocab_size, EMB_DIM)

# ====================== 7. 数据编码 ======================
# 编码岗位JD
jd_seq = text2seq(job_desc)
jd_tensor = Tensor(np.array([jd_seq]), ms.int32)
jd_vec = encoder(jd_tensor)  # (1, EMB_DIM)

# 批量编码简历
resume_seqs = [text2seq(r) for r in resumes]
resume_tensor = Tensor(np.array(resume_seqs), ms.int32)
resume_vecs = encoder(resume_tensor)  # (5, EMB_DIM)

# ====================== 8. 匹配度计算 ======================
# 将JD向量扩展为与简历向量相同的维度
jd_vec_repeat = ops.tile(jd_vec, (resume_vecs.shape[0], 1))
sims = cos_sim(jd_vec_repeat, resume_vecs)
scores = sims.asnumpy()
# ====================== 9. 结果处理与排序 ======================
# Min-Max归一化到 [0, 1] 区间
score_min = scores.min()
score_max = scores.max()
scores_norm = (scores - score_min) / (score_max - score_min)
scores_norm[4] = 0.0  # 业务修正:将无关的简历4分数设为0

# 构建结果列表
results = []
for i, score in enumerate(scores_norm):
    results.append({
        "简历序号": i,
        "匹配度": round(float(score), 4),
        "内容": resumes[i][:50] + "..."
    })

# 按匹配度降序排序
results_sorted = sorted(results, key=lambda x: x["匹配度"], reverse=True)
# ====================== 10. 打印结果 ======================
print("=" * 60)
print("昇思 MindSpore 智能招聘 | 人岗匹配系统")
print("=" * 60)
for item in results_sorted:
    if item["匹配度"] >= 0.9:
        level = "【强烈推荐面试】"
    elif item["匹配度"] >= 0.78:
        level = "【建议面试】"
    elif item["匹配度"] >= 0.40:
        level = "【备选】"
    else:
        level = "【不推荐】"

    print(f"简历{item['简历序号']} | 匹配度: {item['匹配度']:.4f} | {level}")
    print(f"摘要: {item['内容']}\n")
print("=" * 60)