【2025年泰迪杯数据挖掘挑战赛】A题 数据分析+问题建模与求解+Python代码直接分享

news2025/4/19 7:30:57

目录

  • 2025年泰迪杯数据挖掘挑战赛A题
  • 完整论文:建模与求解Python代码
  • 1问题一的思路与求解
    • 1.1 问题一的思路
      • 1.1.1对统计数据进行必要说明:
      • 1.1.2统计流程:
      • 1.1.3特殊情况的考虑:
    • 1.2 问题一的求解
      • 1.2.1代码实现
      • 1.2.2 问题一结果
      • 代码分享
  • 2 问题二的思路与求解
    • 2.1 问题二的思路
      • 2.1.1检测方法的设计:
      • 2.1.2统计流程:
      • 2.1.3特殊情况的考虑:
      • 2.1.4 问题二模型的建立
        • 1. 单棵决策树的构建(CART算法)
        • 2. 随机森林集成
        • 3. 特征重要性计算
        • 4. 概率输出(Soft Voting)
        • 5. 模型决策规则
    • 2.2 问题二的求解
      • 2.2.1特征工程
      • 2.2.2代码实现

2025年泰迪杯数据挖掘挑战赛A题

完整论文:建模与求解Python代码

在这里插入图片描述
在这里插入图片描述

1问题一的思路与求解

1.1 问题一的思路

首先,题目要求对竞赛论文的基本信息进行统计,保存到result1.xlsx中。附件1提供了参赛队伍的信息,其中加密号对应附件3的文件名。
根据附件4中的result1.xlsx模板,需要统计的列包括论文标题、总页数、总字数、摘要页数和字数、目录页数、正文页数和字数,还有图片数、表格数、独立公式数、参考文献数量等。此外,还包括一些比例,比如图片所占比例,以及平均句子数和平均字数等。

1.1.1对统计数据进行必要说明:

  • 总页数:PDF文件的总页数,使用PyPDF2或pdfplumber获取。
  • 总字数:提取所有文本后统计字符数(包括空格和标点)。
  • 摘要页数与字数:定位包含“摘要”或“Abstract”的页面,统计其页数和字数。
  • 目录页数:定位“目录”或“Contents”部分,统计其页数。
  • 正文页数与字数:正文起始页为目录结束页+1,结束于参考文献起始页-1,统计页数和字数。
  • 正文图片数:统计正文中所有Image对象的数量。
  • 正文图片所占比例:正文图片数 / 总图片数。
  • 正文表格数:使用pdfplumber提取表格数量。
  • 正文独立公式数:正则表达式匹配LaTeX公式块(如[…])。
  • 正文段落平均句子数与字数:使用句号、问号、感叹号分句,计算句子数和平均字数。
  • 参考文献数量:统计参考文献部分的条目数(以[数字]开头)。

1.1.2统计流程:

(1)读取PDF文件:使用Python库如PyPDF2、pdfplumber或PyMuPDF来读取PDF内容,提取文本、图片、表格等信息。
(2)提取元数据:获取总页数。
(3)分部分统计页数和字数:

  • 摘要:识别摘要部分,通常位于开头,可能有“摘要”或“Abstract”作为标题。需要找到该部分的起始页和结束页,统计该部分的页数和字数。
  • 目录:查找“目录”或“Contents”部分,统计其页数和字数。
  • 正文:正文可能从引言开始,直到参考文献之前。需要确定正文的起始和结束位置。

(4)统计图片数:遍历每一页,检测PDF中的图片对象,统计总数。
(5)统计表格数:检测PDF中的表格结构,可能需要使用pdfplumber的表格提取功能。
(6)统计独立公式数:可能需要分析文本中的公式模式,如识别被括号包围的公式编号,或者使用正则表达式匹配公式结构。
(7)参考文献数量:找到参考文献部分,统计条目数量,通常每个条目以编号或作者开头。
(8)计算平均句子数和平均字数:以句号、问号、感叹号作为句子分隔符,对正文文本进行分句和分词统计。

1.1.3特殊情况的考虑:

  1. 摘要和目录的灵活匹配:有些论文可能将“摘要”写成“摘 要”或“摘 要”等变体形式,存在空格或其他格式变化。
    解决方案:使用正则表达式模糊匹配,忽略空格和特殊字符。例如,匹配“摘\s要”或“目\s录”。
  2. 正文范围的优化:若参考文献起始页与正文最后一页同一页时,以参考文献的前一页推算正文页数的方法可能错误地将部分正文页排除。
    解决方案:定位参考文献起始段落的位置,而不是整个页面。例如,找到“参考文献”所在的段落起始位置,正文结束于该段落的开始位置。
  3. 独立公式数的统计优化:表格中的公式可能未被检测。
    解决方案:在提取表格内容后,对表格内的文本也进行公式匹配。
  4. PDF为图片型文档的处理:对于整个页面为图片的PDF,无法提取文本,导致字数统计为0。
    解决方案:使用OCR工具(如Tesseract)对图片型PDF进行文本提取。
  5. 标题跨行:如果标题跨越多行,不进行特殊处理只会获取到第一行,标题不完整。
    解决方案:提取第一页的所有行,合并从第一行开始,直到遇到分隔线、空行或特定关键词(如“摘要”)。
  6. 正文页数的判定:若论文格式不规范,当论文没有目录或参考文献时,正文页数会统计错误。
    解决方案:修改判断逻辑:
    正文起始页判定:①如果存在目录,正文从目录页后一页开始;②如果没有目录,则尝试从摘要后的页面开始正文;③如果连摘要也未找到,默认正文从第一页开始(假设标题页后即为正文)。
    正文结束页判定:①仍然以参考文献或附录作为结束标志;②若无参考文献或附录,则以总页数为结束页。

1.2 问题一的求解

1.2.1代码实现

Step1:使用pandas读取附件1.xlsx,创建字典,键为加密号,值为参赛队号和其他信息,从而获取加密号与参赛队号的对应关系。
Step2:遍历附件3中的PDF文件,对于每个PDF文件:
a. 使用pdfplumber打开,获取总页数。
b. 提取所有页面的文本,并存储每页的文本和对象(图片、表格等)。
c. 定位摘要、目录、正文、参考文献部分:

  • 摘要:搜索页面中的“摘要”或“Abstract”,确定其起始页和结束页(可能摘要仅在一页)。
  • 目录:同样通过关键词定位。
  • 正文:可能从目录后的页面开始,或者根据章节标题判断。
  • 参考文献:找到“参考文献”或“References”部分,统计条目数。

d. 统计图片数:遍历每页的images属性,统计总数。
e. 统计表格数:使用pdfplumber的extract_tables(),统计所有页面的表格数量。
f. 独立公式数:使用正则表达式匹配公式块,同时结合公式编号进行考虑。
g. 参考文献数量:统计参考文献部分的条目数。
h. 计算总字数:所有文本的总字符数,或者分词后的词语数。
i. 计算平均句子数和平均字数:对正文文本进行处理。
Step3:保存结果到result1.xlsx:使用pandas将统计结果写入Excel文件。

1.2.2 问题一结果

注意:本文档使用示例数据求解
展示部分输出结果:

在这里插入图片描述

对结果可视化:给大家提供了很多种图像参考,如果图像绘制出来后不明白如何分析,可以将代码和输入数据上传给AI,让它给出建议。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码分享

import os
import pandas as pd
import pdfplumber
import re
from PyPDF2 import PdfReader
from pdf2image import convert_from_path
import pytesseract

def flexible_keyword_match(text, keywords):
    pattern = r'[\s\u3000]*'.join(list(keywords))
    return re.search(pattern, text or '') is not None

def ocr_pdf(pdf_path):
    images = convert_from_path(pdf_path)
    full_text = []
    for img in images:
        text = pytesseract.image_to_string(img, lang='chi_sim+eng')
        full_text.append(text)
    return full_text

def extract_title(first_page_text):
    """提取论文标题,更准确地区分标题与摘要等其他内容"""
    if not first_page_text:
        return '无标题'
    
    # 按行分割
    lines = first_page_text.split('\n')
    non_empty_lines = [line.strip() for line in lines if line.strip()]
    
    # 检查是否有明确的"摘要"标记,这常常是标题与摘要的分界线
    abstract_index = -1
    for i, line in enumerate(non_empty_lines):
        if re.search(r'^摘\s*要$|^摘\s*[要約][\s\::]|^ABSTRACT$|^Abstract$', line):
            abstract_index = i
            break
    
    # 如果找到了摘要标记,则摘要标记之前的行(最多2)可能是标题
    if abstract_index > 0:
        # 一般标题在摘要之前的1-2行
        potential_title_lines = non_empty_lines[max(0, abstract_index-2):abstract_index]
        # 过滤掉明显不是标题的行
        title_lines = []
        for line in potential_title_lines:
            # 过滤掉包含特定关键词的行,如学院、作者等
            if not re.search(r'(学院|大学|指导老师|作者|姓名|学号|导师|系|专业|年级|班级)', line):
                title_lines.append(line)
        
        if title_lines:
            # 取最长的一行作为标题,或者合并多行
            if len(title_lines) == 1 or len(title_lines[0]) > 15:  # 如果只有一行或第一行够长
                return title_lines[0]
            else:
                return ' '.join(title_lines)  # 合并多行标题
    
    # 如果没有找到摘要标记,或者摘要标记之前没有合适的标题行
    # 尝试查找文档最开始的几行中可能的标题
    
    # 1. 论文标题通常是文档开头的1-3行中最长的那一行(长度适中,不会太短也不会太长)
    potential_titles = []
    for i, line in enumerate(non_empty_lines[:5]):  # 只考虑前5行
        line_length = len(line)
        if 4 < line_length < 50 and not re.search(r'(摘要|abstract|关键词|keywords|目录|contents)', line.lower()):
            # 根据行长度和位置计算可能性得分 - 通常标题比较靠前且长度适中
            score = (5-i) + min(line_length, 30)/10  # 位置越靠前分数越高,长度适中加分
            potential_titles.append((line, score))
    
    # 按得分排序
    potential_titles.sort(key=lambda x: x[1], reverse=True)
    
    if potential_titles:
        return potential_titles[0][0]  # 返回得分最高的行
    
    # 2. 如果上述方法失败,尝试更简单的方法:取文档第一个非空行
    if non_empty_lines:
        first_line = non_empty_lines[0]
        # 确保第一行不是太长,如果太长可能是段落而非标题
        if len(first_line) < 100:
            return first_line
        return first_line[:50] + "..."  # 截断过长的行
    
    # 3. 实在找不到合适的标题
    return "无标题"

def count_code_lines(text):
    """计算代码行数"""
    total_lines = 0
    
    # 查找代码块
    code_blocks = re.findall(r'```.*?```', text, re.DOTALL)
    for block in code_blocks:
        lines = block.split('\n')
        # 减去代码块标记
        total_lines += max(0, len(lines) - 2)
    
    # 查找附录中的代码行 - 搜索常见的代码模式
    code_patterns = [
        r'^\s*function\s+\w+',           # 函数定义
        r'^\s*def\s+\w+',                # Python函数定义
        r'^\s*for\s+\w+\s*=',            # for循环
        r'^\s*if\s+',                    # if语句
        r'^\s*else',                     # else语句
        r'^\s*while\s+',                 # while循环
        r'^\s*switch\s+',                # switch语句
        r'^\s*case\s+',                  # case语句
        r'^\s*try\s*{',                  # try块
        r'^\s*catch\s*{',                # catch块
        r'^\s*import\s+',                # import语句
        r'^\s*#include',                 # include语句
        r'^\s*return\s+',                # return语句
        r'^\s*\w+\s*=\s*\w+\(',          # 函数调用赋值
        r'^\s*\w+\s*\+=|\-=|\*=|/=',     # 运算赋值
        r'^\s*\w+\s*\+\+|\-\-',          # 自增自减
        r'^\s*//|^\s*#|^\s*%',           # 注释行
        r'^\s*\w+\s*:\s*$',              # 标签行
        r'^\s*\w+\s*\[.+\]\s*=',         # 数组赋值
    ]
    
    # 检查文本行
    lines = text.split('\n')
    for line in lines:
        line = line.strip()
        # 如果行不在已识别的代码块中,检查是否匹配代码模式
        if any(re.match(pattern, line) for pattern in code_patterns):
            total_lines += 1
            
    return total_lines

def detect_formulas(text):
    """识别文本中的独立数学公式"""
    formula_count = 0
    
    # 查找标准公式模式 (LaTeX风格的公式)
    latex_formulas = re.findall(r'\$\$.*?\$\$|\\\[.*?\\\]', text, re.DOTALL)
    formula_count += len(latex_formulas)
    
    # 按行分析文本
    lines = text.split('\n')
    i = 0
    while i < len(lines):
        line = lines[i].strip()
        
        # 跳过空行或太短的行
        if not line or len(line) < 3:
            i += 1
            continue
            
        # 跳过标题、章节号等
        if re.match(r'^[【(\[【\d\.]|^参考文献|^附录|^表\s|^图\s', line):
            i += 1
            continue
        
        # 检查是否是独立公式行(前后都是空行或段落分隔)
        is_isolated_line = False
        if i > 0 and i < len(lines) - 1:
            prev_line = lines[i-1].strip()
            next_line = lines[i+1].strip()
            is_isolated_line = (not prev_line or prev_line.endswith('.')) and (not next_line or re.match(r'^[A-Z\u4e00-\u9fff]', next_line))
        
        # 独立公式行的特征检查
        if is_isolated_line:
            # 1. 包含典型数学符号
            has_math_symbols = bool(re.search(r'[+\-*/=()[\]{}∫∑∏∇∆]|∂|∇', line))
            
            # 2. 包含典型公式结构(如变量和运算符的组合)
            has_formula_structure = bool(re.search(r'[a-zA-Z][\s]*[+\-*/=><]', line))
            
            # 3. 有公式编号或居中对齐
            has_formula_numbering = bool(re.search(r'\(\d+\)$|\[\d+\]$', line))
            
            # 4. 行只包含公式相关内容(不含普通文本的整句)
            is_formula_only = not re.search(r'[。,;:?!""()]', line) and len(line.split()) <= 5
            
            # 公式需满足的条件组合
            if has_math_symbols and has_formula_structure and (has_formula_numbering or is_formula_only):
                formula_count += 1
                i += 1
                continue
        
        # 检查连续的多行公式(当前行和下一行都有数学符号且没有普通句子标点)
        if i < len(lines) - 1:
            next_line = lines[i+1].strip()
            if (re.search(r'[+\-*/=()[\]{}]', line) and 
                re.search(r'[+\-*/=()[\]{}]', next_line) and 
                not re.search(r'[。,;:?!""()]', line) and
                not re.search(r'[。,;:?!""()]', next_line)):
                
                # 确认是否为延续性公式(一般公式比普通文本短,且两行都没有完整句)
                if len(line) < 60 and len(next_line) < 60 and not line.endswith('。'):
                    formula_count += 1
                    i += 2  # 跳过下一行,因为它是公式的一部分
                    continue
        
        i += 1
    
    return formula_count

def detect_appendix_pages(full_text):
    """识别附录页面并计算附录页数"""
    appendix_start = None
    
    # 查找附录开始的页码
    for i, text in enumerate(full_text):
        if re.search(r'^附录\s*$|^附录[::\s]|^\d+[\s\.、]*附录', text.strip()) or re.search(r'附\s*录', text[:20]):
            appendix_start = i
            break
    
    # 如果找不到明确的"附录"标题,尝试查找"参考文献"之后的内容
    if appendix_start is None:
        ref_end = None
        for i, text in enumerate(full_text):
            if re.search(r'^参考文献\s*$|^参考文献[::\s]|^\d+[\s\.、]*参考文献', text.strip()):
                ref_end = i
                # 在参考文献之后查找可能是附录的内容
                for j in range(ref_end + 1, len(full_text)):
                    if re.search(r'附录\s*\d+|^附\s*\d+|程序代码|附\s*代码|附表|附图|附\s*[A-Za-z]', full_text[j]):
                        appendix_start = j
                        break
                break
    
    # 如果仍然找不到附录,查找最后一个"参考文献"之后的所有内容
    if appendix_start is None:
        for i in reversed(range(len(full_text))):
            if re.search(r'^参考文献\s*$|^参考文献[::\s]|^\d+[\s\.、]*参考文献', full_text[i].strip()):
                # 确保参考文献不是最后一页
                if i + 1 < len(full_text):
                    appendix_start = i + 1
                break
    
    # 如果找到了附录开始页
    if appendix_start is not None and appendix_start < len(full_text):
        appendix_pages = len(full_text) - appendix_start
        
        # 检查是否是真正的附录内容
        appendix_content = ' '.join(full_text[appendix_start:])
        has_appendix_content = (
            re.search(r'附录\s*\d+|附\s*\d+|附表|附图|程序代码|代码清单|function|def\s+\w+|import\s+|#include|void\s+\w+\s*\(', appendix_content) or
            len(re.findall(r'\b(for|if|while|return|print|int|float|char|string)\b', appendix_content)) > 3
        )
        
        if has_appendix_content:
            return appendix_pages, appendix_start
        
    # 默认情况下返回0(没有找到附录)和None(没有找到附录开始页)
    return 0, None

def count_references(text):
    """统计参考文献数量,考虑多种常见引用格式"""
    # 定义可能的参考文献格式模式
    patterns = [
        r'[\[\(]\d+[\]\)]',          # 匹配[1](1)等标准格式
        r'【\d+】',                   # 匹配【1】等中文方括号格式
        r'^\s*\[\d+\]',              # 匹配行首的[1]等(适用于参考文献列表)
        r'^\s*【\d+】',               # 匹配行首的【1】等
        r'^\s*\(\d+\)',              # 匹配行首的(1)等
        r'^\s*\d+\.\s'               # 匹配行首的"1. "等格式
    ]
    
    # 将参考文献文本分割成行,便于处理行首模式
    lines = text.split('\n')
    
    # 对于行首模式(最后三种模式),直接在行中计数
    line_count = 0
    for line in lines:
        if (re.match(r'^\s*\[\d+\]', line) or 
            re.match(r'^\s*【\d+】', line) or 
            re.match(r'^\s*\(\d+\)', line) or 
            re.match(r'^\s*\d+\.\s', line)):
            if re.search(r'[A-Za-z\u4e00-\u9fff]', line):  # 确保有实际内容
                line_count += 1
    
    # 如果通过行首模式找到了参考文献,直接返回
    if line_count > 0:
        return line_count
    
    # 否则使用第一种和第二种模式进行全文搜索
    # 注意:这种方法可能会过度计数,因为正文中也可能有[1]这样的引用
    pattern_count = len(re.findall(r'[\[\(]\d+[\]\)]', text)) + len(re.findall(r'【\d+】', text))
    
    # 还可以尝试查找格式化的参考文献格式,如"[1] 作者名"
    formatted_count = len(re.findall(r'([\[\(]\d+[\]\)]|【\d+】)\s+[A-Za-z\u4e00-\u9fff]', text))
    
    # 根据实际情况返回更合理的计数
    if formatted_count > 0:
        return formatted_count
    return pattern_count

# 读取参赛队信息
df_teams = pd.read_excel('附件1.xlsx', sheet_name='Sheet1')
team_mapping = df_teams.set_index('加密号')['参赛队号'].to_dict()

results = []
pdf_folder = '附件3/'

for filename in os.listdir(pdf_folder):
    if not filename.endswith('.pdf'):
        continue
    
    encryption_code = filename.split('.')[0]
    team_number = team_mapping.get(encryption_code, '未知')
    
    pdf_path = os.path.join(pdf_folder, filename)
    with pdfplumber.open(pdf_path) as pdf:
        # OCR处理判断
        first_page_text = pdf.pages[0].extract_text()
        if not first_page_text:
            full_text = ocr_pdf(pdf_path)
        else:
            full_text = [page.extract_text() or '' for page in pdf.pages]
        
        total_pages = len(pdf.pages)
        total_words = sum(len(text) for text in full_text)
        
        # 摘要统计
        abstract_pages, abstract_words = 0, 0
        for i, text in enumerate(full_text):
            if flexible_keyword_match(text, '摘要') or flexible_keyword_match(text, 'Abstract'):
                abstract_pages += 1
                abstract_words += len(text)
                break  # 假设摘要只占一页
        
        # 目录统计
        toc_pages, toc_words = 0, 0
        for i, text in enumerate(full_text):
            if flexible_keyword_match(text, '目录') or flexible_keyword_match(text, 'Contents'):
                toc_pages += 1
                toc_words += len(text)
                break
        
        # 修改正文起始页的判定逻辑
        body_start = None
        
        # 情况1:存在目录
        for i, text in enumerate(full_text):
            if flexible_keyword_match(text, '目录') or flexible_keyword_match(text, 'Contents'):
                body_start = i + 1
                break
        
        # 情况2:没有目录,但存在摘要
        if body_start is None:
            for i, text in enumerate(full_text):
                if flexible_keyword_match(text, '摘要') or flexible_keyword_match(text, 'Abstract'):
                    # 假设摘要可能跨多页,正文从摘要最后一页的下一页开始
                    abstract_end = i
                    body_start = abstract_end + 1
                    break
        
        # 情况3:既无目录也无摘要,默认正文从第一页开始
        if body_start is None:
            body_start = 0  # 或根据实际情况跳过标题页(如 body_start = 1)
        
        # 查找参考文献部分
        ref_start_page = None
        for i, text in enumerate(full_text):
            if (re.search(r'^参考文献\s*$|^参考文献[::\s]|^\d+[\s\.、]*参考文献', text.strip()) or 
                re.search(r'^References\s*$|^REFERENCES\s*$', text.strip())):
                ref_start_page = i
                break
        
        # 找不到明确的"参考文献"标题,尝试其他方法查找
        if ref_start_page is None:
            # 搜索包含参考文献的行
            for i, text in enumerate(full_text):
                if re.search(r'参\s*考\s*文\s*献', text[:50]):  # 只在页面开头部分查找
                    ref_start_page = i
                    break
        
        # 从参考文献开始到结束的所有页面中提取文本
        ref_count = 0
        if ref_start_page is not None:
            # 获取参考文献部分至文档结束的文本
            ref_text = '\n'.join(full_text[ref_start_page:])
            
            # 尝试限定参考文献部分(避免计算后续章节)
            next_section_match = re.search(r'\n(附录|appendix|附\s*录|APPENDIX)\s*\n', ref_text, re.IGNORECASE)
            if next_section_match:
                ref_text = ref_text[:next_section_match.start()]
                
            # 计算参考文献数量
            ref_count = count_references(ref_text)
            
            # 如果数量不合理(例如太大),尝试使用更严格的计数方法
            if ref_count > 50:  # 假设超过50篇引用不太常见
                ref_text_lines = ref_text.split('\n')
                strict_count = 0
                for line in ref_text_lines:
                    # 只计算那些看起来像参考文献条目开头的行
                    if (re.match(r'^\s*\[\d+\]', line) or 
                        re.match(r'^\s*【\d+】', line) or 
                        re.match(r'^\s*\(\d+\)', line) or 
                        re.match(r'^\s*\d+\.\s', line)):
                        if re.search(r'[A-Za-z\u4e00-\u9fff]', line):  # 确保有实际内容
                            strict_count += 1
                if strict_count > 0:
                    ref_count = strict_count
        
        # 使用新的附录检测函数
        appendix_pages, appendix_start = detect_appendix_pages(full_text)
        
        # 确定正文结束页 - 需要考虑参考文献和附录的位置
        body_end = total_pages  # 默认正文结束于最后一页
        
        # 如果找到参考文献,正文结束于参考文献开始页
        if ref_start_page is not None and ref_start_page > body_start:
            body_end = ref_start_page
        # 如果找到附录且附录在正文起始页之后,正文结束于附录开始页
        elif appendix_start is not None and appendix_start > body_start:
            body_end = appendix_start
        
        # 确保body_start和body_end合法,避免负数页数
        if body_start is None:
            body_start = 0
        if body_end <= body_start:
            body_end = body_start + 1  # 至少保证有1页正文
            
        body_pages = body_end - body_start
        body_words = sum(len(full_text[i]) for i in range(body_start, body_end))
        
        # 计算附录代码行数
        appendix_code_lines = 0
        if appendix_start is not None:
            # 获取附录文本
            appendix_text = ''.join(full_text[appendix_start:])
            # 计算代码行数:包括代码块和直接在文本中显示的代码行
            appendix_code_lines = count_code_lines(appendix_text)
            
            # 如果没有明确的代码块,通过关键词和缩进来识别代码行
            if appendix_code_lines == 0:
                lines = appendix_text.split('\n')
                for line in lines:
                    # 检查是否有代码行的特征,如function、forif、end等关键词
                    if (re.search(r'^\s*(function|for|if|while|switch|case|else|end|return|try|catch)', line.strip()) or
                        re.search(r'[{}();=%]', line) or
                        re.search(r'^\s*[a-zA-Z0-9_]+\s*=', line)):
                        appendix_code_lines += 1
        
        # 图片统计(全文和正文)
        total_image_count = 0
        for i in range(total_pages):
            total_image_count += len(pdf.pages[i].images)
        
        body_image_count = 0
        for i in range(body_start, body_end):
            if i < total_pages:  # 确保索引不超出范围
                body_image_count += len(pdf.pages[i].images)
        
        # 计算正文图片所占比例
        image_ratio = body_image_count / total_image_count if total_image_count > 0 else 0
        
        # 表格和公式统计(只统计正文)
        body_tables = 0
        body_formula_count = 0
        
        # 提取正文文本用于公式识别
        body_text = ''.join(full_text[body_start:body_end])
        body_formula_count = detect_formulas(body_text)
        
        # 统计表格数量
        for i in range(body_start, body_end):
            if i < total_pages:  # 确保索引不超出范围
                page = pdf.pages[i]
                tables = page.extract_tables()
                body_tables += len(tables)
                
                # 检查表格内容中的公式
                for table in tables:
                    if table:
                        for row in table:
                            if row:
                                for cell in row:
                                    if cell:
                                        table_formula_count = detect_formulas(str(cell))
                                        body_formula_count += table_formula_count
        
        # 参考文献数量
        ref_count = 0
        if ref_start_page is not None:
            ref_text = full_text[ref_start_page]
            # 查找常见的参考文献标记格式
            ref_patterns = [
                r'\[\d+\]',             # [1], [2], ...
                r'【\d+】',              # 【1,2, ...
                r'\(\d+\)',             # (1), (2), ...
                r'^\d+\.\s',            # 1. , 2. , ... (每行开头)
                r'^\[\d+\]\s',          # [1] , [2] ... (每行开头)
                r'^\d+\s[A-Z]',         # 1 Author, 2 Author, ... (数字开头的引用)
            ]
            
            # 分行处理以便更好地识别每个文献条目
            lines = ref_text.split('\n')
            for line in lines:
                line = line.strip()
                for pattern in ref_patterns:
                    if re.search(pattern, line):
                        ref_count += 1
                        break  # 找到一个匹配就跳出内层循环,避免重复计数
            
            # 如果仍未找到参考文献,尝试在全文本中查找
            if ref_count == 0 and ref_start_page < len(full_text) - 1:
                # 查看参考文献页之后的一页或几页
                for i in range(ref_start_page, min(ref_start_page + 3, len(full_text))):
                    additional_text = full_text[i]
                    lines = additional_text.split('\n')
                    for line in lines:
                        line = line.strip()
                        for pattern in ref_patterns:
                            if re.search(pattern, line):
                                ref_count += 1
                                break
        
        # 正文段落统计
        body_text = ''.join(full_text[body_start:body_end]) if body_start is not None else ''
        # 根据换行符分割段落,每个换行符代表一个新段落
        paragraphs = re.split(r'\n', body_text)
        paragraphs = [p.strip() for p in paragraphs if p.strip()]
        paragraph_count = len(paragraphs)
        
        # 统计句子数和字数
        total_sentences = 0
        for para in paragraphs:
            sentences = re.split(r'[。!?]', para)
            sentences = [s.strip() for s in sentences if s.strip()]
            total_sentences += len(sentences)
        
        avg_sentences_per_para = total_sentences / paragraph_count if paragraph_count > 0 else 0
        avg_words_per_para = body_words / paragraph_count if paragraph_count > 0 else 0
        
        # 保存结果
        results.append({
            '加密号': encryption_code,
            '参赛队号': team_number,
            '论文标题': extract_title(full_text[0]) if full_text else '无标题',
            '总页数': total_pages,
            '总字数': total_words,
            '摘要页数': abstract_pages,
            '摘要字数': abstract_words,
            '目录页数': toc_pages,
            '正文页数': body_pages,
            '正文字数': body_words,
            '正文图片数': body_image_count,
            '正文图片所占比例': image_ratio,
            '正文表格数': body_tables,
            '正文独立公式数': body_formula_count,
            '正文段落数': paragraph_count,
            '正文段落平均句子数': avg_sentences_per_para,
            '正文段落平均字数': avg_words_per_para,
            '参考文献数量': ref_count,
            '附录页数': appendix_pages,
            '附录代码行数': appendix_code_lines,
        })

# 输出到Excel
pd.DataFrame(results).to_excel('resultC.xlsx', index=False)

2 问题二的思路与求解

2.1 问题二的思路

题目要求从所有竞赛论文中筛选出包含参赛队信息的论文,判断正文内容是否与赛题无关,以及识别无实质内容的论文。问题分解为三个子任务:
1.筛选包含参赛队信息的论文:需要验证论文中是否包含附件1中的参赛队信息,比如队号、队员姓名等。
2.判断正文是否与赛题无关:需要对比论文内容与附件2中的赛题描述,确认论文是否围绕赛题展开。
3.识别无实质内容的论文:需要分析论文是否有足够的实际内容,比如是否包含具体的方法、数据分析和结果,而不仅仅是重复题目或背景信息。

2.1.1检测方法的设计:

针对每个子任务,需要设计相应的检测方法:

  1. 包含参赛队信息的检测:
    需要将附件1中的参赛队信息作为关键词,然后在论文中搜索这些关键词。通过正则表达式匹配附件1中的参赛队号、队员姓名、指导教师等信息。例如,附件1中的参赛队号格式为“20201000001”,队员姓名可能以中文或特定编号形式出现。
  2. 判断正文是否与赛题无关:
    需要判断论文内容是否涉及直肠癌、淋巴结转移、CT影像分割等关键词,使用自然语言处理技术(如TF-IDF或BERT)提取论文的关键词和主题,嵌入模型计算论文与赛题的相似度。设定相似度阈值,低于阈值则判定为与赛题无关(相似度可以设定为论文中提取的关键词与题目关键词的相似度)。例如,B5978.pdf的内容是关于运输车辆安全驾驶的,明显与赛题无关。
  3. 识别无实质内容的论文:
    (1)分析论文的结构完整性(是否包含研究方法、数据分析、结果等),检查是否包含数学公式、图表、算法描述等专业内容。这里可以利用问题一生成的result1.xlsx中的统计信息来增强判断的准确性。
    (2)评估内容的原创性,例如,B1523的论文内容完全复制了赛题描述,没有自己的研究内容,因此属于无实质内容。要识别这类论文,可能需要检测论文中是否存在大量重复的赛题内容,或者是否有足够的原创内容。例如,可以计算论文正文与赛题描述(附件2)的相似度,如果相似度过高,则可能无实质内容。
    注意:需要区分实质性内容与模板化描述,例如目录、参考文献等部分可能不包含实际分析。

2.1.2统计流程:

  1. 判断是否包含参赛队信息:
    (1)提取附件1中的所有参赛队信息,包括队号、学校名、指导教师、队员等。
    (2)对每篇论文的全文进行文本提取,并检查是否包含这些信息中的任意一个。
    (3)如果存在匹配,则标记为1,否则为0。
  2. 判断是否与赛题无关:
    (1)提取赛题(附件2)的关键词,如直肠癌、淋巴结、CT、分割、影像特征等。
    (2)对论文进行文本分析,提取关键词或主题,计算与赛题关键词的相似度。
    (3)如果相似度低于某个阈值,则标记为1(无关),否则为0。
  3. 判断是否无实质内容:
    (1)分析论文的结构,如是否有摘要、方法、实验、结果等部分。
    (2)检测论文中是否大量复制赛题内容,比如与附件2中的描述高度重复。
    (3)如果论文的原创内容过少,或者大部分内容与赛题描述重复,则标记为1。

2.1.3特殊情况的考虑:

  1. 需要处理不同格式的参赛队信息,例如队号可能以不同方式书写,队员姓名可能存在错别字或缩写。
  2. 参赛队信息可能分散在论文的不同位置(如页眉、页脚或正文),需要全面扫描整个文档。
  3. 赛题描述可能包含专业术语,需要确保模型能准确识别相关主题,避免误判。
  4. 赛题无关的判断可能受论文结构影响,例如有些论文可能包含少量相关关键词但整体不相关。
  5. 无实质内容的论文可能包含大量重复内容或模板文字,需要设计有效的特征来捕捉这些模式。

2.1.4 问题二模型的建立

1. 单棵决策树的构建(CART算法)
2. 随机森林集成
3. 特征重要性计算
4. 概率输出(Soft Voting)
5. 模型决策规则

2.2 问题二的求解

2.2.1特征工程

2.2.2代码实现

Step1:读取附件1中的所有参赛队信息,生成一个关键词列表,然后使用PDF文本提取工具(如PyPDF2或pdfplumber)提取每篇论文的文本,进行关键词匹配。
Step2:需要构建赛题的关键词列表,然后计算论文中的关键词频率,或者使用文本相似度方法(如余弦相似度)来比较论文内容与赛题描述。
Step3:可能需要计算论文与赛题文本的重复比例。例如,使用文本相似度检测,如Jaccard相似度或TF-IDF向量化后的余弦相似度。如果相似度过高,说明论文大量复制赛题内容,属于无实质内容。此外,还可以检查论文字数,如果正文过短,可能内容不足。

完整论文与代码,请看下方

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2336735.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NO.95十六届蓝桥杯备战|图论基础-单源最短路|负环|BF判断负环|SPFA判断负环|邮递员送信|采购特价产品|拉近距离|最短路计数(C++)

P3385 【模板】负环 - 洛谷 如果图中存在负环&#xff0c;那么有可能不存在最短路。 BF算法判断负环 执⾏n轮松弛操作&#xff0c;如果第n轮还存在松弛操作&#xff0c;那么就有负环。 #include <bits/stdc.h> using namespace std;const int N 2e3 10, M 3e3 1…

在机器视觉检测中为何选择线阵工业相机?

线阵工业相机&#xff0c;顾名思义是成像传感器呈“线”状的。虽然也是二维图像&#xff0c;但极宽&#xff0c;几千个像素的宽度&#xff0c;而高度却只有几个像素的而已。一般在两种情况下使用这种相机&#xff1a; 1. 被测视野为细长的带状&#xff0c;多用于滚筒上检测的问…

Windows 下 MongoDB ZIP 版本安装指南

在开发和生产环境中&#xff0c;MongoDB 是一种非常流行的 NoSQL 数据库&#xff0c;以其灵活性和高性能而受到开发者的青睐。对于 Windows 用户来说&#xff0c;MongoDB 提供了多种安装方式&#xff0c;其中 ZIP 版本因其灵活性和轻量级的特点&#xff0c;成为很多开发者的首选…

2025年十六届蓝桥杯Python B组原题及代码解析

相关试题可以在洛谷上测试用例&#xff1a; 2025 十六届 蓝桥杯 Python B组 试题 A&#xff1a;攻击次数 答案&#xff1a;103 print(103)代码&#xff1a; # 初始化敌人的血量 x 2025# 初始化回合数 turn 0# 模拟攻击过程 while x > 0:# 回合数加一turn 1# 第一个英…

数据清洗到底在清洗什么?

在大数据时代&#xff0c;数据是每个企业的五星资产&#xff0c;被誉为“新石油”&#xff0c;但未经处理的数据往往参杂着大量“杂质”。这些“脏数据”不仅影响分析结果&#xff0c;严重的甚至误导企业决策。数据清洗作为数据预处理的关键环节&#xff0c;正是通过“去芜存菁…

Microsoft Azure 基础知识简介

Microsoft Azure 基础知识简介 已完成100 XP 2 分钟 Microsoft Azure 是一个云计算平台&#xff0c;提供一系列不断扩展的服务&#xff0c;可帮助你构建解决方案来满足业务目标。 Azure 服务支持从简单到复杂的一切内容。 Azure 具有简单的 Web 服务&#xff0c;用于在云中托…

数据库ALGORITHM = INSTANT研究过程

背景 偶然在团队中发现同事大量使用 ALGORITHM INSTANT 更新字段&#xff0c;根据固有的理解&#xff0c;平时字段的更新必然会涉及到表结构的更改&#xff0c;印象中数据库会加入MDL锁去保证表数据的一致性。 但是听说在Mysql8.0特性中&#xff0c;表明在更新字段的时候此方法…

n8n 为技术团队打造的安全工作流自动化平台

AI MCP 系列 AgentGPT-01-入门介绍 Browser-use 是连接你的AI代理与浏览器的最简单方式 AI MCP(大模型上下文)-01-入门介绍 AI MCP(大模型上下文)-02-awesome-mcp-servers 精选的 MCP 服务器 AI MCP(大模型上下文)-03-open webui 介绍 是一个可扩展、功能丰富且用户友好的…

基于Python的App流量大数据分析与可视化方案

一、引言 App流量数据通常包括用户的访问时间、停留时间、点击行为、页面跳转路径等信息。这些数据分散在不同的服务器日志、数据库或第三方数据平台中&#xff0c;需要通过有效的技术手段进行整合和分析。Python在数据科学领域的广泛应用&#xff0c;得益于其简洁的语法、强大…

【Linux 并发与竞争实验】

【Linux 并发与竞争实验】 之前学习了四种常用的处理并发和竞争的机制&#xff1a;原子操作、自旋锁、信号量和互斥体。本章我们就通过四个实验来学习如何在驱动中使用这四种机制。 文章目录 【Linux 并发与竞争实验】1.原子操作实验1.1 实验程序编写1.2 运行测试 2.自旋锁实验…

wx219基于ssm+vue+uniapp的教师管理系统小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…

leetcode0079. 单词搜索-medium

1 题目&#xff1a; 单词搜索 官方标定难度&#xff1a;中 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字…

SvelteKit 最新中文文档教程(20)—— 最佳实践之性能

前言 Svelte&#xff0c;一个语法简洁、入门容易&#xff0c;面向未来的前端框架。 从 Svelte 诞生之初&#xff0c;就备受开发者的喜爱&#xff0c;根据统计&#xff0c;从 2019 年到 2024 年&#xff0c;连续 6 年一直是开发者最感兴趣的前端框架 No.1&#xff1a; Svelte …

在多系统环境中实现授权闭环,Tetra Pak 借助CodeMeter打造食品工业的安全自动化体系

一、 行业背景与安全新挑战 在食品加工自动化不断深化的背景下&#xff0c;食品安全、功能安全与知识产权保护的需求日益迫切。Tetra Pak 作为全球领先的食品加工和包装解决方案提供商&#xff0c;业务遍布 160 多个国家&#xff0c;涵盖从配料混合、碳酸化处理到全线自动包装。…

B端可视化方案,如何助力企业精准决策,抢占市场先机

在当今竞争激烈的商业环境中&#xff0c;企业需要快速、准确地做出决策以抢占市场先机。B端可视化方案通过将复杂的企业数据转化为直观的图表和仪表盘&#xff0c;帮助企业管理层和业务人员快速理解数据背后的业务逻辑&#xff0c;从而做出精准决策。本文将深入探讨B端可视化方…

0701表单组件-react-仿低代码平台项目

文章目录 1 react表单组件1.1 受控组件 (Controlled Components)示例代码&#xff1a; 1.2 非受控组件 (Uncontrolled Components)示例代码&#xff1a; 2 AntD表单组件实战2.1 开发搜索功能2.2 开发注册页2.3 开发登录页2.4 表单组件校验 结语 1 react表单组件 input表单组件…

【adb】bat批处理+adb 自动亮屏,自动解锁屏幕,启动王者荣耀

准备adb 下载 需要确认是否安装了adb.exe文件,可以在: 任务管理器 -->详细信息–>找一下后台运行的adb 安装过anroid模拟器,也存在adb,例如:雷电安装目录 D:\leidian\LDPlayer9 单独下载adb 官方下载地址:[官方网址] 下载目录文件: 测试adb USB连接手机 首先在设置界…

Distortion, Animation Raymarching

这节课的主要目的是对uv进行操作&#xff0c;实现一些动画的效果&#xff0c;实际就是采样的动画 struct texDistort {float2 texScale(float2 uv, float2 scale){float2 texScale (uv - 0.5) * scale 0.5;return texScale;}float2 texRotate(float2 uv, float angle){float…

SpringBoot整合POI实现Excel文件的导出与导入

使用 Apache POI 操作 Excel文件,系列文章: 《SpringBoot整合POI实现Excel文件的导出与导入》 《SpringMVC实现文件的上传与下载》 《C#使用NPOI导出Excel文件》 《NPOI使用手册》 1、Apache POI 的介绍 Apache POI 是一个基于 Java 的开源库,专为读写 Microsoft Office 格…

矩阵基础+矩阵转置+矩阵乘法+行列式与逆矩阵

GPU渲染过程 矩阵 什么是矩阵&#xff08;Matrix&#xff09; 向量 &#xff08;3&#xff0c;9&#xff0c;88&#xff09; 点乘&#xff1a;计算向量夹角 叉乘&#xff1a;计算两个向量构成平面的法向量。 矩阵 矩阵有3行&#xff0c;2列&#xff0c;所以表示为M32 获取固…