Python+AI 大模型实现课堂教学质量智能分析|加权评分 + 自动诊断 + 改进建议

目录

一、项目背景

二、系统架构

三、核心技术

四、完整代码实现

五、运行效果展示

六、项目亮点

七、适用场景

八、总结


本文基于Python 数据分析、加权欧氏距离算法、AI 大模型,实现一套课堂教学质量自动评分 + 问题智能诊断 + 改进建议生成系统。可自动分析师生行为、教学话语、师生情绪三大维度,输出专业课堂评估报告,适用于智慧教育、课堂诊断、教研分析等场景。

一、项目背景

在智慧教育快速发展的今天,课堂教学质量评估越来越依赖数据化、智能化、自动化。传统评课依赖人工经验,效率低、主观性强、难以规模化。

为此,我开发了一套全自动课堂分析系统,实现:

  1. 自动读取课堂分析 CSV 数据
  2. 多维度指标自动评分(师生行为、话语、情绪)
  3. 基于加权欧氏距离计算专业得分
  4. AI 大模型自动生成课堂问题分析
  5. AI 自动输出可落地的课堂改进策略

最终输出一份完整、专业、可直接用于教研的课堂评估报告

二、系统架构

整个系统分为两大核心模块:

模块 1:课堂自动评分模块(CalculateScore)

读取 4 类 CSV 分析结果

加权权重加载

加权欧氏距离计算差异度

自动计算三大维度得分 + 综合得分

模块 2:AI 课堂诊断模块(ProblemImprovement)

读取指标数据

调用 AI 大模型生成问题分析

AI 自动生成结构化改进建议

输出完整报告文本

三、核心技术

Python CSV 数据解析

加权欧氏距离评分算法

JSON 权重配置

上下文管理器统一读取文件 / 内存流

OpenAI 兼容接口调用大模型

AI 提示词工程(高质量输出)

四、完整代码实现

  1. 课堂评分核心代码
python 复制代码
# 课堂多维度自动评分器
class ClassroomScorer:
    def __init__(self, weight_data=None, csv_files=None):
        # 初始化权重、CSV路径、缓存
        pass
    
    # 读取CSV(支持文件/内存流)
    @contextmanager
    def _read_csv_source(self, source):
        pass
    
    # 加载课堂数据
    def _load_classroom_data(self):
        pass
    
    # 加载权重
    def _load_weight_data(self):
        pass
    
    # 加权差异度计算
    def _calculate_weighted_difference(self):
        pass
    
    # 最终得分计算
    def _calculate_final_score(self):
        pass
    
    # 获取详细报告
    def get_detailed_report(self):
        pass

总代码

python 复制代码
import csv
import json
import numpy as np
import math
import os
import io
from contextlib import contextmanager

class ClassroomScorer:
    """课堂评分器类,用于计算课堂教学质量评估分数"""
    
    def __init__(self, weight_data=None, csv_files=None):
        """初始化课堂评分器
        
        Args:
            weight_data: 权重数据,可以是:
                        1. 字典格式的权重数据(直接使用)
                        2. 文件路径字符串(从文件读取)
                        3. None(使用默认文件路径)
            csv_files: CSV文件字典,支持两种value类型:
                      1. io.StringIO对象(优先级更高)
                      2. 文件路径字符串
                      格式: {
                          '师生行为': io.StringIO对象 或 'path/to/师生行为.csv',
                          '话语形式': io.StringIO对象 或 'path/to/话语形式.csv', 
                          '话语功能': io.StringIO对象 或 'path/to/话语功能.csv',
                          '师生情绪': io.StringIO对象 或 'path/to/师生情绪.csv'
                      }
        """
        # 处理权重数据
        if isinstance(weight_data, dict):  #判断一个变量 是不是 某种数据类型
            # 直接使用传入的权重字典
            self.weight_data = weight_data
            self.weight_file_path = None
        elif isinstance(weight_data, str):
            # 使用传入的文件路径
            self.weight_data = None
            self.weight_file_path = weight_data
        elif weight_data is None:
            # 使用默认文件路径
            self.weight_data = None
            self.weight_file_path = r"D:\software\Pycharm\语音转文字\话语分析\local_weight_vectors_20250801_115218.json"
        else:
            raise TypeError("weight_data参数必须是字典、字符串路径或None")
        
        # 设置CSV文件路径
        if csv_files is None:
            # 默认路径
            self.csv_files = {
                '师生行为': r".\doc\师生行为.csv",
                '话语形式': r".\doc\话语形式.csv",
                '话语功能': r".\doc\话语功能.csv",
                '师生情绪': r".\doc\师生情绪.csv"
            }
        elif isinstance(csv_files, dict):
            self.csv_files = csv_files
        else:
            raise TypeError("csv_files参数必须是字典类型")
        
        # 缓存数据,避免重复读取
        self._classroom_data = None
        self._local_weights = None
    
    def _validate_csv_files(self):
        """验证CSV文件是否存在(仅对文件路径进行验证)"""
        missing_files = []
        for name, file_source in self.csv_files.items():
            # 如果是字符串路径,检查文件是否存在
            if isinstance(file_source, str) and not os.path.exists(file_source):
                missing_files.append(f"{name}: {file_source}")
        
        if missing_files:
            raise FileNotFoundError(f"以下CSV文件不存在: {missing_files}")


    @contextmanager  # @contextmanager:把函数变成上下文管理器,支持 with 调用
    def _read_csv_source(self, source):
        """
        读取CSV数据源的上下文管理器,统一处理两种数据源:
        1. 内存中的 io.StringIO 对象
        2. 本地文件路径字符串
        """
        # 判断数据源是否为内存文本流 io.StringIO 类型
        if isinstance(source, io.StringIO):
            # 重置流的读取指针到开头(防止流已被读取过导致读不到数据)
            source.seek(0)
            # 生成 csv.DictReader 对象,按行读取为字典格式
            yield csv.DictReader(source)

        # 判断数据源是否为文件路径字符串
        elif isinstance(source, str):
            # 以只读方式打开文件,使用 utf-8-sig 编码兼容带BOM的UTF-8文件
            with open(source, 'r', encoding='utf-8-sig') as file_obj:
                # 生成 csv.DictReader 对象,按行读取为字典格式
                yield csv.DictReader(file_obj)

        # 既不是StringIO也不是路径,抛出不支持类型错误
        else:
            raise TypeError(f"不支持的CSV数据源类型: {type(source)}")
    
    def _load_classroom_data(self):
        """从四个CSV数据源中读取课堂数据,返回字典格式的三个部分数据"""
        if self._classroom_data is not None:
            return self._classroom_data
        
        # 验证文件存在性(仅对文件路径)
        self._validate_csv_files()
            
        # 初始化三个部分的数据字典
        classroom_data = {
            '师生行为': {'test_vector': [], 'quality_vector': []},
            '教学话语': {'test_vector': [], 'quality_vector': []},
            '师生情绪': {'test_vector': [], 'quality_vector': []}
        }
        
        try:
            # 1. 处理师生行为数据 
            behavior_source = self.csv_files['师生行为']
            with self._read_csv_source(behavior_source) as reader:
                for row in reader:
                    if row.get('维度') != '维度':  # 跳过标题行
                        second_level = row.get('二级指标', '').strip() 
                        if second_level:  # 确保二级指标不为空
                            # 读取占比和优质课数据
                            ratio_str = row.get('占比', '').strip()
                            quality_str = row.get('优质课', '').strip()
                            
                            if ratio_str and quality_str:
                                if ratio_str.endswith('%') and quality_str.endswith('%'):  # 是不是都以百分号 % 结尾
                                    try:
                                        ratio_value = float(ratio_str[:-1]) # 截取,去掉%
                                        quality_value = float(quality_str[:-1])
                                        classroom_data['师生行为']['test_vector'].append(ratio_value)
                                        classroom_data['师生行为']['quality_vector'].append(quality_value)
                                    except ValueError as e:
                                        print(f"警告:师生行为数据格式错误 - {ratio_str}, {quality_str}: {e}")
            
            # 2. 处理话语形式数据
            form_source = self.csv_files['话语形式']
            with self._read_csv_source(form_source) as reader:
                for row in reader:
                    if row.get('维度') != '维度':  # 跳过标题行
                        first_level = row.get('一级指标', '').strip()
                        if first_level:  # 确保一级指标不为空
                            # 读取占比和优质课数据
                            ratio_str = row.get('汇总', '').strip()
                            quality_str = row.get('优质课', '').strip()
                            
                            if ratio_str and quality_str:
                                if ratio_str.endswith('%') and quality_str.endswith('%'):
                                    try:
                                        ratio_value = float(ratio_str[:-1])
                                        quality_value = float(quality_str[:-1])
                                        classroom_data['教学话语']['test_vector'].append(ratio_value)
                                        classroom_data['教学话语']['quality_vector'].append(quality_value)
                                    except ValueError as e:
                                        print(f"警告:话语形式数据格式错误 - {ratio_str}, {quality_str}: {e}")
            
            # 3. 处理话语功能数据
            function_source = self.csv_files['话语功能']
            with self._read_csv_source(function_source) as reader:
                for row in reader:
                    if row.get('维度') != '维度':  # 跳过标题行
                        first_level = row.get('一级指标', '').strip()
                        if first_level:  # 确保一级指标不为空
                            # 读取汇总和优质课数据
                            summary_str = row.get('汇总', '').strip()
                            quality_str = row.get('优质课', '').strip()
                            
                            if summary_str and quality_str:
                                if summary_str.endswith('%') and quality_str.endswith('%'):
                                    try:
                                        summary_value = float(summary_str[:-1])
                                        quality_value = float(quality_str[:-1])
                                        classroom_data['教学话语']['test_vector'].append(summary_value)
                                        classroom_data['教学话语']['quality_vector'].append(quality_value)
                                    except ValueError as e:
                                        print(f"警告:话语功能数据格式错误 - {summary_str}, {quality_str}: {e}")
            
            # 4. 处理师生情绪数据
            emotion_source = self.csv_files['师生情绪']
            with self._read_csv_source(emotion_source) as reader:
                for row in reader:
                    if row.get('维度') != '维度':  # 跳过标题行
                        second_level = row.get('二级指标', '').strip()
                        if second_level:  # 确保二级指标不为空
                            # 读取占比和优质课数据
                            ratio_str = row.get('占比', '').strip()
                            quality_str = row.get('优质课', '').strip()
                            
                            if ratio_str and quality_str:
                                if ratio_str.endswith('%') and quality_str.endswith('%'):
                                    try:
                                        ratio_value = float(ratio_str[:-1])
                                        quality_value = float(quality_str[:-1])
                                        classroom_data['师生情绪']['test_vector'].append(ratio_value)
                                        classroom_data['师生情绪']['quality_vector'].append(quality_value)
                                    except ValueError as e:
                                        print(f"警告:师生情绪数据格式错误 - {ratio_str}, {quality_str}: {e}")
            
            # 缓存数据
            self._classroom_data = classroom_data
            return classroom_data
            
        except Exception as e:
            raise RuntimeError(f"读取课堂数据时发生错误: {e}")
        
        # 验证数据完整性
        # self._validate_classroom_data(classroom_data)
        
        # 缓存数据
        self._classroom_data = classroom_data
        return classroom_data
    
    def _validate_classroom_data(self, classroom_data):
        """验证课堂数据的完整性"""
        for section_name, section_data in classroom_data.items():
            test_len = len(section_data['test_vector'])
            quality_len = len(section_data['quality_vector'])
            
            if test_len == 0 or quality_len == 0:
                raise ValueError(f"{section_name}部分没有有效数据")
            
            if test_len != quality_len:
                raise ValueError(f"{section_name}部分的待测课向量和优质课向量长度不匹配: {test_len} vs {quality_len}")
    
    def _load_weight_data(self):
        """读取局部权重数据"""
        if self._local_weights is not None:
            return self._local_weights
        
        # 如果已经有权重数据字典,直接使用
        if self.weight_data is not None:
            local_weights = self.weight_data
            
            # 如果传入的是完整的权重数据结构,提取local_weight_vectors
            if 'local_weight_vectors' in local_weights:
                local_weights = local_weights['local_weight_vectors']
            
            # 验证权重数据完整性
            required_sections = ['师生行为', '教学话语', '师生情绪']
            missing_sections = [section for section in required_sections if section not in local_weights]
            
            if missing_sections:
                raise KeyError(f"权重数据中缺少以下部分的权重数据: {missing_sections}")
            
            self._local_weights = local_weights
            return local_weights
        
        # 否则从文件读取
        if not os.path.exists(self.weight_file_path):
            raise FileNotFoundError(f"未找到权重文件: {self.weight_file_path},请确保权重文件存在")
            
        try:
            with open(self.weight_file_path, 'r', encoding='utf-8') as file:
                data = json.load(file)
            
            # 提取局部权重向量
            if 'local_weight_vectors' not in data:
                raise KeyError("权重文件中缺少 'local_weight_vectors' 字段")
            
            local_weights = data['local_weight_vectors']
            
            # 验证权重数据完整性
            required_sections = ['师生行为', '教学话语', '师生情绪']
            missing_sections = [section for section in required_sections if section not in local_weights]
            
            if missing_sections:
                raise KeyError(f"权重文件中缺少以下部分的权重数据: {missing_sections}")
            
            self._local_weights = local_weights
            return local_weights
            
        except json.JSONDecodeError as e:
            raise ValueError(f"权重文件格式错误: {e}")
        except Exception as e:
            raise RuntimeError(f"读取权重文件时发生错误: {e}")
    
    def _calculate_weighted_difference(self, test_vector, quality_vector, weights):
        """计算加权差异度:利用加权欧氏距离
        Dw = √Σi Wi⋅(ei-ti)²
        其中:ei为第i项指标的参考向量值(优质课),ti为待评估课堂的对应指标值,Wi为预设的指标权重系数
        """
        if len(test_vector) != len(quality_vector) or len(test_vector) != len(weights):
            raise ValueError(f"向量长度不匹配: test={len(test_vector)}, quality={len(quality_vector)}, weights={len(weights)}")
        
        weighted_sum = 0
        for i in range(len(test_vector)):
            # 计算差值的平方
            diff_squared = (quality_vector[i] - test_vector[i]) ** 2
            # 加权
            weighted_sum += weights[i] * diff_squared
        
        # 计算加权欧氏距离
        weighted_distance = math.sqrt(weighted_sum)
        
        return weighted_distance
    
    def _calculate_final_score(self, dw, dmax):
        """计算最终得分:S = 100 * (1 - Dw/dmax)
        其中:Dw为加权差异度,dmax为理论最大差异度
        """
        if dmax == 0:
            return 100  # 如果dmax为0,说明没有差异,得分为满分
        
        score = 100 * (1 - dw / dmax)
        # 确保得分在0-100范围内
        score = max(0, min(100, score))
        
        return score
    
    def _calculate_theoretical_max_difference(self, quality_vector, weights):
        """计算理论最大差异度dmax
        假设待测课堂向量全为0(最差情况),优质课向量为实际值
        """
        weighted_sum = 0
        for i in range(len(quality_vector)):
            # 最大差异为优质课向量值本身(假设待测课为0)
            diff_squared = quality_vector[i] ** 2
            weighted_sum += weights[i] * diff_squared
        
        dmax = math.sqrt(weighted_sum)
        return dmax
    
    def get_scores(self):
        """计算并返回课堂评估分数列表 [师生行为, 教学话语, 师生情绪]"""
        # 读取课堂数据(字典格式)
        classroom_data = self._load_classroom_data()
        
        # 读取局部权重数据
        local_weights = self._load_weight_data()
        
        scores = []
        
        # 计算师生行为部分得分
        test_behavior = classroom_data['师生行为']['test_vector']
        quality_behavior = classroom_data['师生行为']['quality_vector']
        weights_behavior = local_weights['师生行为']
        
        dw_behavior = self._calculate_weighted_difference(test_behavior, quality_behavior, weights_behavior)
        dmax_behavior = self._calculate_theoretical_max_difference(quality_behavior, weights_behavior)
        score_behavior = self._calculate_final_score(dw_behavior, dmax_behavior)
        scores.append(score_behavior)
        
        # 计算教学话语部分得分
        test_discourse = classroom_data['教学话语']['test_vector']
        quality_discourse = classroom_data['教学话语']['quality_vector']
        weights_discourse = local_weights['教学话语']
        
        dw_discourse = self._calculate_weighted_difference(test_discourse, quality_discourse, weights_discourse)
        dmax_discourse = self._calculate_theoretical_max_difference(quality_discourse, weights_discourse)
        score_discourse = self._calculate_final_score(dw_discourse, dmax_discourse)
        scores.append(score_discourse)
        
        # 计算师生情绪部分得分
        test_emotion = classroom_data['师生情绪']['test_vector']
        quality_emotion = classroom_data['师生情绪']['quality_vector']
        weights_emotion = local_weights['师生情绪']
        
        dw_emotion = self._calculate_weighted_difference(test_emotion, quality_emotion, weights_emotion)
        dmax_emotion = self._calculate_theoretical_max_difference(quality_emotion, weights_emotion)
        score_emotion = self._calculate_final_score(dw_emotion, dmax_emotion)
        scores.append(score_emotion)
        
        return scores
    
    def get_detailed_report(self):
        """获取详细的评分报告"""
        # 读取课堂数据(修正:使用字典格式)
        classroom_data = self._load_classroom_data()
        local_weights = self._load_weight_data()
        
        # 获取分数
        scores = self.get_scores()
        score_behavior, score_discourse, score_emotion = scores
        
        # 构建向后兼容的向量格式
        all_test_vector = (classroom_data['师生行为']['test_vector'] + 
                          classroom_data['教学话语']['test_vector'] + 
                          classroom_data['师生情绪']['test_vector'])
        all_quality_vector = (classroom_data['师生行为']['quality_vector'] + 
                             classroom_data['教学话语']['quality_vector'] + 
                             classroom_data['师生情绪']['quality_vector'])
        
        report = {
            '总体信息': {
                '待测课向量': all_test_vector,
                '优质课向量': all_quality_vector
            },
            '师生行为': {
                '待测课向量': classroom_data['师生行为']['test_vector'],
                '优质课向量': classroom_data['师生行为']['quality_vector'],
                '权重向量': local_weights['师生行为'],
                '得分': round(score_behavior, 2)
            },
            '教学话语': {
                '待测课向量': classroom_data['教学话语']['test_vector'],
                '优质课向量': classroom_data['教学话语']['quality_vector'],
                '权重向量': local_weights['教学话语'],
                '得分': round(score_discourse, 2)
            },
            '师生情绪': {
                '待测课向量': classroom_data['师生情绪']['test_vector'],
                '优质课向量': classroom_data['师生情绪']['quality_vector'],
                '权重向量': local_weights['师生情绪'],
                '得分': round(score_emotion, 2)
            },
            '总结': {
                '师生行为得分': round(score_behavior, 2),
                '教学话语得分': round(score_discourse, 2),
                '师生情绪得分': round(score_emotion, 2)
            }
        }
        
        return report
    
    def print_detailed_report(self):
        """打印详细的评分报告"""
        try:
            report = self.get_detailed_report()
            
            print(f"待测课向量 T = {report['总体信息']['待测课向量']}")
            print(f"优质课向量 Q = {report['总体信息']['优质课向量']}")
            
            print("\n=== 师生行为部分 ===")
            print(f"待测课向量: {report['师生行为']['待测课向量']}")
            print(f"优质课向量: {report['师生行为']['优质课向量']}")
            print(f"权重向量: {report['师生行为']['权重向量']}")
            print(f"师生行为得分: {report['师生行为']['得分']}")
            
            print("\n=== 教学话语部分 ===")
            print(f"待测课向量: {report['教学话语']['待测课向量']}")
            print(f"优质课向量: {report['教学话语']['优质课向量']}")
            print(f"权重向量: {report['教学话语']['权重向量']}")
            print(f"教学话语得分: {report['教学话语']['得分']}")
            
            print("\n=== 师生情绪部分 ===")
            print(f"待测课向量: {report['师生情绪']['待测课向量']}")
            print(f"优质课向量: {report['师生情绪']['优质课向量']}")
            print(f"权重向量: {report['师生情绪']['权重向量']}")
            print(f"师生情绪得分: {report['师生情绪']['得分']}")
            
            print("\n=== 总结 ===")
            # print(f"师生行为得分: {report['总结']['师生行为得分']}")
            # print(f"教学话语得分: {report['总结']['教学话语得分']}")
            # print(f"师生情绪得分: {report['总结']['师生情绪得分']}")
            a = {
                '师生行为得分': report['总结']['师生行为得分'],
                '教学话语得分': report['总结']['教学话语得分'],
                '师生情绪得分': report['总结']['师生情绪得分'],
                '综合课堂得分': (report['总结']['师生行为得分']+report['总结']['教学话语得分']+report['总结']['师生情绪得分'])/3
            }
            return a
            
        except Exception as e:
            print(f"生成报告时发生错误: {e}")





if __name__ == "__main__":

    csv_files = {
        '师生行为': r".\doc\师生行为.csv",
        '话语形式': r".\doc\话语形式.csv",
        '话语功能': r".\doc\话语功能.csv",
        '师生情绪': r".\doc\师生情绪.csv"
    }
    # csv_files = {
    #     '师生行为': r"D:\soft\pycharm\code\pythonProject\1_10bsd项目\1_整体接口\师生情绪与行为\20250824233636_师生行为.csv",
    #     '话语形式': r"D:\soft\pycharm\code\pythonProject\1_10bsd项目\1_整体接口\话语形式分析结果\话语形式_总结_20250825_210001.csv",
    #     '话语功能': r"D:\soft\pycharm\code\pythonProject\1_10bsd项目\1_整体接口\话语功能分析结果\话语功能_分类结果汇总_20250825_210049.csv",
    #     '师生情绪': r"D:\soft\pycharm\code\pythonProject\1_10bsd项目\1_整体接口\师生情绪与行为\20250824233636_师生情绪.csv"
    # }
    weight_data = r"D:\software\Pycharm\语音转文字\话语分析\local_weight_vectors_20250801_115218.json"
    try:
        # 创建评分器实例
        scorer = ClassroomScorer(csv_files=csv_files,weight_data=weight_data)

        # 打印详细报告
        a = scorer.print_detailed_report()
        print(a)

    except Exception as e:
        print(f"程序执行错误: {e}")
  1. AI 课堂诊断代码
python 复制代码
# AI课堂问题分析与改进建议生成
class ProblemImprovement:
    def __init__(self, api_key=None, csv_files=None):
        # 初始化大模型、指标体系
        pass
    
    # 读取各类CSV数据
    def extract_teacher_student_data(self):
        pass
    
    # 构建待测课/优质课数据字典
    def get_dict(self):
        pass
    
    # AI生成问题分析
    def get_problem_analysis(self):
        pass
    
    # AI生成改进建议
    def get_improvement_suggestions(self):
        pass

总代码

python 复制代码
# -*- coding: utf-8 -*-
import openai
import json
import csv
import os
from typing import Dict, List, Tuple
import re


class ProblemImprovement:
    def __init__(self, api_key=None, csv_files=None, weight_file_path=None):
        self.api = api_key
        self.client = openai.OpenAI(
            api_key=self.api,
            base_url="https://dashscope.aliyuncs.com/compatible-mode/v1")
        self.indicators = {
            '师生行为': ['全班讲解', '个别指导', '教师巡视', '学生听讲', '学生回答', '学生做题', '小组活动',
                         '汇报展示'],
            '关键能力': ['知识理解', '表达交流', '实践应用', '创造迁移'],
            '关键行为': ['陈述', '提问', '回答', '反馈', '管理'],
            '情感体验': ['积极', '中性', '消极', '积极', '中性', '消极', '积极']
        }
        self.weight_file_path = weight_file_path
        self.csv_files = csv_files

    def read_csv_data(self, file_path: str, column_name: str) -> List[str]:
        """读取CSV文件并返回指定列的数据列表"""
        data = []
        try:
            with open(file_path, 'r', encoding='utf-8') as file:
                # 跳过可能的BOM字符
                content = file.read()
                content = content.lstrip('\ufeff')

                # 重新解析CSV
                reader = csv.reader(content.splitlines())
                headers = next(reader)  # 读取表头

                # 查找目标列的索引
                try:
                    column_index = headers.index(column_name)
                except ValueError:
                    # 如果找不到指定列名,尝试查找相似的列
                    for i, header in enumerate(headers):
                        if column_name in header:
                            column_index = i
                            break
                    else:
                        raise ValueError(f"在文件 {file_path} 中找不到列: {column_name}")

                # 读取数据
                for row in reader:
                    if len(row) > column_index and row[column_index].strip():
                        # 处理百分比格式
                        value = row[column_index].strip().rstrip('%')
                        try:
                            # 尝试转换为浮点数
                            float_value = float(value)
                            data.append(float_value)
                        except ValueError:
                            # 如果无法转换为数字,保留原始值
                            data.append(value)

        except Exception as e:
            raise Exception(f"读取CSV文件 {file_path} 时出错: {str(e)}")
        return data

    def extract_teacher_student_data(self, file_path: str) -> Dict[str, List[float]]:
        """提取师生行为数据"""
        result = {'test': [], 'quality': []}
        try:
            with open(file_path, 'r', encoding='utf-8') as file:
                content = file.read()
                content = content.lstrip('\ufeff')  # 去除BOM头
                reader = csv.reader(content.splitlines())
                headers = next(reader)
                # 查找列索引
                ratio_index = -1
                quality_index = -1
                for i, header in enumerate(headers):
                    if '占比' in header:
                        ratio_index = i
                    elif '优质课' in header:
                        quality_index = i
                if ratio_index == -1 or quality_index == -1:
                    raise ValueError("找不到占比或优质课列")
                # 读取所有有效数据行(排除标题行)
                for row in reader:
                    # 跳过空行
                    if not row:
                        continue
                    try:
                        # 提取百分比数值
                        test_value = float(row[ratio_index].strip().rstrip('%'))
                        quality_value = float(row[quality_index].strip().rstrip('%'))

                        # 转为小数并存入结果
                        result['test'].append(test_value / 100)
                        result['quality'].append(quality_value / 100)
                    except (ValueError, IndexError):
                        continue

        except Exception as e:
            raise Exception(f"提取师生行为数据时出错: {str(e)}")

        return result

    def extract_discourse_data(self, file_path: str, is_form: bool = False) -> Dict[str, List[float]]:
        """提取话语形式或话语功能数据"""
        result = {'test': [], 'quality': []}
        try:
            with open(file_path, 'r', encoding='utf-8') as file:
                content = file.read()
                content = content.lstrip('\ufeff')  # 去除BOM头
                reader = csv.reader(content.splitlines())
                headers = next(reader)

                # 查找列索引:汇总列 = 测试占比,优质课列不变
                ratio_index = -1
                quality_index = -1

                for i, header in enumerate(headers):
                    if '汇总' in header:  # 汇总列才是你要的一级指标占比
                        ratio_index = i
                    elif '优质课' in header:
                        quality_index = i

                if ratio_index == -1 or quality_index == -1:
                    raise ValueError("找不到汇总占比或优质课列")

                # 读取每一行,提取【汇总列有值】的行
                for row in reader:
                    if not row:
                        continue

                    try:
                        # 只提取 汇总列 有数字的行(就是一级指标汇总行)
                        ratio_val = row[ratio_index].strip()
                        quality_val = row[quality_index].strip()

                        if ratio_val and quality_val:
                            test_value = float(ratio_val.rstrip('%'))
                            quality_value = float(quality_val.rstrip('%'))

                            result['test'].append(test_value / 100)
                            result['quality'].append(quality_value / 100)
                    except (ValueError, IndexError):
                        continue

        except Exception as e:
            raise Exception(f"提取话语数据时出错: {str(e)}")
        return result


    def extract_emotion_data(self, file_path: str) -> Dict[str, List[float]]:
        """提取情感体验数据"""
        result = {'test': [], 'quality': []}
        try:
            with open(file_path, 'r', encoding='utf-8') as file:
                content = file.read()
                content = content.lstrip('\ufeff')
                reader = csv.reader(content.splitlines())
                headers = next(reader)

                # 查找占比和优质课列的索引
                ratio_index = -1
                quality_index = -1
                for i, header in enumerate(headers):
                    if '占比' in header:
                        ratio_index = i
                    elif '优质课' in header:
                        quality_index = i

                if ratio_index == -1 or quality_index == -1:
                    raise ValueError("找不到占比或优质课列")

                # 读取教师情绪和学生情绪数据
                teacher_emotions = []
                student_emotions = []
                student_attention = []

                for row in reader:
                    if len(row) > max(ratio_index, quality_index):
                        if row[1] == '教师情绪' and row[2] in ['积极', '中性', '消极']:
                            try:
                                test_value = float(row[ratio_index].strip().rstrip('%'))
                                quality_value = float(row[quality_index].strip().rstrip('%'))
                                teacher_emotions.append((test_value / 100, quality_value / 100))
                            except (ValueError, IndexError):
                                continue
                        elif row[1] == '学生情绪' and row[2] in ['积极', '中性', '消极']:
                            try:
                                test_value = float(row[ratio_index].strip().rstrip('%'))
                                quality_value = float(row[quality_index].strip().rstrip('%'))
                                student_emotions.append((test_value / 100, quality_value / 100))
                            except (ValueError, IndexError):
                                continue
                        elif row[1] == '学生注意力' and row[2] == '积极':
                            try:
                                test_value = float(row[ratio_index].strip().rstrip('%'))
                                quality_value = float(row[quality_index].strip().rstrip('%'))
                                student_attention.append((test_value / 100, quality_value / 100))
                            except (ValueError, IndexError):
                                continue

                # 按照情感体验的顺序组合数据
                for test, quality in teacher_emotions:
                    result['test'].append(test)
                    result['quality'].append(quality)

                for test, quality in student_emotions:
                    result['test'].append(test)
                    result['quality'].append(quality)

                for test, quality in student_attention:
                    result['test'].append(test)
                    result['quality'].append(quality)

        except Exception as e:
            raise Exception(f"提取情感体验数据时出错: {str(e)}")
        return result

    def get_dict(self) -> tuple:
        """从四个CSV文件读取课堂评估数据,返回待测课和优质课的指标占比字典"""
        try:
            # 初始化向量字典
            test_vectors = {}
            quality_vectors = {}

            # 处理师生行为数据
            if '师生行为' in self.csv_files and os.path.exists(self.csv_files['师生行为']):
                behavior_data = self.extract_teacher_student_data(self.csv_files['师生行为'])
                test_vectors['师生行为'] = behavior_data['test']
                quality_vectors['师生行为'] = behavior_data['quality']

            # 处理话语功能数据(对应关键能力)
            if '话语功能' in self.csv_files and os.path.exists(self.csv_files['话语功能']):
                discourse_function_data = self.extract_discourse_data(self.csv_files['话语功能'])
                test_vectors['关键能力'] = discourse_function_data['test']
                quality_vectors['关键能力'] = discourse_function_data['quality']

            # 处理话语形式数据(对应关键行为)
            if '话语形式' in self.csv_files and os.path.exists(self.csv_files['话语形式']):
                discourse_form_data = self.extract_discourse_data(self.csv_files['话语形式'], is_form=True)
                test_vectors['关键行为'] = discourse_form_data['test']
                quality_vectors['关键行为'] = discourse_form_data['quality']

            # 处理师生情绪数据(对应情感体验)
            if '师生情绪' in self.csv_files and os.path.exists(self.csv_files['师生情绪']):
                emotion_data = self.extract_emotion_data(self.csv_files['师生情绪'])
                test_vectors['情感体验'] = emotion_data['test']
                quality_vectors['情感体验'] = emotion_data['quality']

            # 构建待测课字典和优质课字典(指标:占比)
            test_dict = {}
            quality_dict = {}

            # 按照indicators的顺序处理数据
            for dimension, indicators_list in self.indicators.items():
                if dimension in test_vectors:
                    test_values = test_vectors[dimension]
                    # 确保指标数量与向量长度匹配
                    for i, indicator in enumerate(indicators_list):
                        if i < len(test_values):
                            test_dict[indicator] = test_values[i]

                if dimension in quality_vectors:
                    quality_values = quality_vectors[dimension]
                    # 确保指标数量与向量长度匹配
                    for i, indicator in enumerate(indicators_list):
                        if i < len(quality_values):
                            quality_dict[indicator] = quality_values[i]

            # 返回元组:(待测课字典, 优质课字典)
            return test_dict, quality_dict

        except FileNotFoundError as e:
            raise FileNotFoundError(f"找不到必要的数据文件: {str(e)}")
        except Exception as e:
            raise Exception(f"读取数据时出错: {str(e)}")

    def get_problem_analysis(self) -> str:
        """获取问题分析部分"""
        test_dict, quality_dict = self.get_dict()

        prompt = f"""
# 任务
你的核心任务是通过对比待测课和优质课的指标占比,对课堂教学中存在的主要问题和不足进行分析。

# 输出要求
通过对比待测课和优质课的指标占比,分析课堂教学中存在的主要问题和不足。具体为:"数据表明,本节课各项指标表现...,但在...仍有一定的改进空间。例如,...环节虽...,但...,该环节...占比低于优质课,...。在...层面,虽然...,但...,...,特别是...有待加强。...环节主要是...,...类话语较少,表现为...,缺乏...。此外,..."

# 关键要求
- 背景要求: 必须先对比待测课和优质课的指标占比,了解该课堂中老师采用的教学方式,以及学生的学习情况。知晓待测课在不同方面与优质课的差距。
- 结构要求: 必须以制表符(\t)或4个空格开头实现首行缩进。内容的格式遵从每个要素中"具体为:"后面的内容。
- 内容要求: 必须生成一个内容完整连贯的段落,不准分段。
- 格式要求: 严禁使用任何形式的列表(如 1, 2, 3)、分点符号(•)或JSON格式。 输出内容除了这段文字外,不应包含任何标题、解释、前言或任何其他额外标记。
- 段落要求: 最多只允许输出内容只有一个换行符,不允许有两个及以上。

#输出示例:
\t数据表明,本节课各项指标表现已贴近优质课的水平,但在关键教学环节仍有一定的改进空间。例如,课堂导入环节教师虽能有效创设情境,但学生思维启动过程稍显局促,该环节个人表达类的话语占比低于优质课,更多依赖教师单向引导,未能充分展现学生从生活经验到数学概念的自主建构过程。在活动实施层面,虽设计多元探究任务,但部分活动未能有效激发全体学生的认知投入,特别是基础薄弱学生的思维卷入度有待加强。课堂总结环节主要是对知识点的归纳总结,比较归纳、迁移创新、情感态度类话语较少,表现为对知识体系的整合力度不足,缺乏对概率概念与生活决策、统计方法之间的结构化关联阐释。此外,信息技术工具的应用尚停留在基础演示功能,未能充分发挥数字技术对认知难点突破和思维可视化的支持作用。

# 下面是你需要对比的待测课和优质课指标占比:
待测课指标占比: {test_dict}
优质课指标占比: {quality_dict}
"""

        try:
            response = self.client.chat.completions.create(
                model="deepseek-v3",
                messages=[
                    {
                        "role": "system",
                        "content": "你是一个擅长对课堂进行问题分析的分析助手。"
                    },
                    {
                        "role": "user",
                        "content": f"{prompt}"
                    }
                ],
                max_tokens=1500,
                temperature=0.5
            )

            response = response.choices[0].message.content.strip()
            result = {
                "status": "success",
                "problem_analysis": response,
            }

            return result

        except Exception as e:
            # 返回错误信息的JSON格式
            error_result = {
                "status": "error",
                "error_message": str(e),
            }
            return error_result

    def get_improvement_suggestions(self) -> str:
        """获取改进建议部分"""
        test_dict, quality_dict = self.get_dict()
        problem_analysis_result = self.get_problem_analysis()
        problem_analysis = problem_analysis_result.get("problem_analysis", "") if isinstance(problem_analysis_result,
                                                                                             dict) else problem_analysis_result

        prompt = f"""
# 任务
你的核心任务是通过对比待测课和优质课的指标占比,针对课堂教学的问题分析结果提出具体的改进策略和实施方案。

# 输出要求
针对发现的问题提出具体的改进策略和实施方案。

# 关键要求
- 背景要求: 必须先对比待测课和优质课的指标占比,读取问题分析来了解该课堂中存在的问题。
- 内容要求: 都是小标题+具体改进建议的形式。严禁使用除了(一)(二)等中文序号外的其他列表形式、分点符号或JSON格式。
- 输出内容除了改进建议外,不应包含任何额外的标题、解释、前言或其他标记。
- 段落要求: 最多只允许输出内容出现一个换行,不允许出现两个及以上的换行符,一定要注意。

#输出示例:
(一)情境导入环节的思维激活优化:
    教师在足球点球情境的引入上已成功激发学生兴趣,可进一步激活学生的思维水平,触及概率比较的本质。例如构建三级问题链深化思考:第一级抛出对比情境"球员A过去10次进7球,球员B过去5次进3球,优先选派谁?",利用磁贴投票暴露学生"仅比较绝对值"的思维惯性;第二级制造认知冲突"若A参赛100次进70球,B参赛50次进30球,谁更稳定?",引导学生发现比率比较的价值;第三级抽象转化"如何用数学工具量化可能性?",借助动态决策天平教具,左侧放置历史进球数卡片,右侧生成概率数值,实现生活问题向数学概念的思维过渡。
(二)核心概念建构的认知冲突设计:
    公式生成环节教师引导学生发现概率计算方法,建议进一步加强对"等可能性"前提的认知建构。例如增设非常规骰子对比实验:提供3D打印的重心偏移骰子,让学生分组收集50次投掷数据,与标准骰子实验对比。当学生发现偏移骰子出现6点的频率高达28%时(理论值16.7%),引导其自主总结"等可能性"对古典概型的重要性。利用双屏互动装置,左屏呈现理论计算过程,右屏动态绘制实验频率折线图,用红色标注偏差超过10%的数据点,强化视觉认知冲击。
(三)迁移应用的真实情境重构:
    概率计算练习已完成基础目标,建议增加真实决策场景的迁移训练。例如将情境改造为校园歌手大赛晋级方案设计给定甲班28人、乙班24人、丙班20人的报名数据,要求设计"公平且利于本校选手"的抽签规则。通过角色扮演(组委会代表、参赛学生、家长监督员)展开三方辩论,在"概率计算(如甲班晋级概率=28/72)""实际公平性""操作可行性"的多维博弈中,理解概率的工具性与局限性。利用AR沙盘实时生成不同方案的概率分布云图,直观呈现决策影响。
(四)技术工具的认知深化应用:
    现有技术使用多停留在数据展示层面,建议发挥深层认知建构作用。例如开发智能概率模拟器,设置两大核心功能:一是"实验参数调节器",允许学生滑动调整实验次数(10-10000次)、骰子偏心度等变量,同步生成概率分布玫瑰图;二是"认知误区捕捉器",当出现"10次实验得3次6点就断言概率30%"等错误时,自动触发对比模块一一左侧回放该组实验片段,右侧叠加大数据模拟的100 万次实验频率收敛动画,通过视觉反差揭示"小样本误差"本质。

# 下面是你需要对比的待测课和优质课指标占比以及问题分析结果:
待测课指标占比: {test_dict}
优质课指标占比: {quality_dict}
问题分析结果: {problem_analysis}
"""

        try:
            response = self.client.chat.completions.create(
                model="deepseek-v3",
                messages=[
                    {
                        "role": "system",
                        "content": "你是一个擅长提供课堂改进建议的分析助手。"
                    },
                    {
                        "role": "user",
                        "content": f"{prompt}"
                    }
                ],
                max_tokens=1500,
                temperature=0.5
            )

            response = response.choices[0].message.content.strip()
            response = re.sub(r'\n\s*\n', '\n', response)  # 移除多个连续换行
            response = re.sub(r'^[\\t ]+', '', response, flags=re.MULTILINE)

            result = {
                "status": "success",
                "improvement_suggestions": response,
            }

            return result

        except Exception as e:
            # 返回错误信息的JSON格式
            error_result = {
                "status": "error",
                "error_message": str(e),
            }
            return error_result


if __name__ == "__main__":
    # 设置你的OpenAI API密钥
    API_KEY = "sk-358a054218ff4741a254022eeeb56b04"  # 请替换为你的实际API密钥
    csv_files = {
        '师生行为': r".\doc\师生行为.csv",
        '话语形式': r".\doc\话语形式.csv",
        '话语功能': r".\doc\话语功能.csv",
        '师生情绪': r".\doc\师生情绪.csv"
    }

    weight_file_path = r'.\result\weight\local_weight_vectors_20250801_115218.json'

    # 创建总结器实例
    problem_improvement = ProblemImprovement(api_key=API_KEY, csv_files=csv_files, weight_file_path=weight_file_path)

    try:
        # 处理口水稿文件
        problem_analysis = problem_improvement.get_problem_analysis()["problem_analysis"]
        improvement_suggestions = problem_improvement.get_improvement_suggestions()["improvement_suggestions"]
        print("问题分析:", problem_analysis)
        print("改进建议:", improvement_suggestions)
    except Exception as e:
        print(f"处理失败: {str(e)}")

五、运行效果展示

  1. 自动评分输出结果
bash 复制代码
C:\Users\Dell\AppData\Local\Programs\Python\Python39\python.exe D:\software\Pycharm\语音转文字\话语分析\calculate_score.py 
待测课向量 T = [53.0, 24.0, 32.0, 34.0, 33.0, 18.0, 29.0, 11.0, 3.3, 19.1, 35.2, 33.8, 8.6, 78.3, 2.2, 19.6, 0.0, 70.0, 20.0, 10.0, 65.0, 25.0, 10.0]
优质课向量 Q = [49.0, 28.0, 30.0, 30.0, 36.0, 15.0, 33.0, 14.0, 12.0, 14.0, 34.0, 33.0, 7.0, 51.0, 20.0, 22.0, 7.0, 85.0, 10.0, 5.0, 85.0, 15.0, 5.0]

=== 师生行为部分 ===
待测课向量: [53.0, 24.0, 32.0, 34.0, 33.0, 18.0, 29.0, 11.0]
优质课向量: [49.0, 28.0, 30.0, 30.0, 36.0, 15.0, 33.0, 14.0]
权重向量: [0.1861550061664629, 0.14593097982317685, 0.16791401401036032, 0.11385959210352276, 0.1167235300069847, 0.0726401383722469, 0.1167235300069847, 0.08005320951026097]
师生行为得分: 89.63

=== 教学话语部分 ===
待测课向量: [3.3, 19.1, 35.2, 33.8, 8.6, 78.3, 2.2, 19.6, 0.0]
优质课向量: [12.0, 14.0, 34.0, 33.0, 7.0, 51.0, 20.0, 22.0, 7.0]
权重向量: [0.09795474399771706, 0.09795474399771704, 0.06499306480911246, 0.09701990597177323, 0.14207754122368021, 0.1288106084655074, 0.15210192308588644, 0.14216286325684574, 0.07692460519176048]
教学话语得分: 51.94

=== 师生情绪部分 ===
待测课向量: [70.0, 20.0, 10.0, 65.0, 25.0, 10.0]
优质课向量: [85.0, 10.0, 5.0, 85.0, 15.0, 5.0]
权重向量: [0.19915215095913286, 0.13123933861512038, 0.16960851042574673, 0.15011599018428204, 0.14633878740167885, 0.2035452224140391]
师生情绪得分: 76.54

=== 总结 ===
{'师生行为得分': 89.63, '教学话语得分': 51.94, '师生情绪得分': 76.54, '综合课堂得分': 72.70333333333333}

2.AI 自动生成课堂问题分析

bash 复制代码
C:\Users\Dell\AppData\Local\Programs\Python\Python39\python.exe D:\software\Pycharm\语音转文字\话语分析\problem_improvement.py 
问题分析: 数据表明,本节课各项指标表现与优质课存在一定差距,但在教学环节设计和学生参与度上仍有一定的改进空间。例如,全班讲解环节虽占比高于优质课,但学生回答和小组活动占比低于优质课,该环节学生互动性不足,更多依赖教师单向输出,未能充分激发学生的主动思考和合作探究。在认知层次层面,虽然知识理解占比显著高于优质课,但表达交流、实践应用和创造迁移占比明显不足,特别是创造迁移类活动完全缺失,高阶思维培养有待加强。师生互动环节主要是以回答和反馈为主,陈述和提问类话语较少,表现为教师引导性提问不足,缺乏对学生深度思考的激发。此外,学生情感状态中积极和中性占比均低于优质课,课堂氛围的活跃度和学生投入度有待提升。
改进建议: (一)优化全班讲解环节的师生互动:将教师单向讲解时间压缩至45%以下,每8分钟插入"思考-配对-分享"环节。例如讲解三角函数定义时,先让学生独立绘制单位圆示意图,再与邻座交换批注,最后随机抽选3组用实物投影仪展示并互评,教师仅针对共性错误进行精讲。
(二)提升小组活动的认知参与度:重组4人异质小组时指定角色分工(记录员、发言员、材料员、计时员),每组配备可视化思维工具包。如概率单元可发放双色计数筹码和统计转盘,要求用筹码构建二项分布模型后,用转盘验证理论概率,最终形成图文报告张贴于教室"数学发现墙"。
(三)强化汇报展示的思维外显:将展示环节占比提升至13%,采用"3×3演讲法"------每组3分钟展示需包含1个核心观点、1个验证过程、1个现实应用。例如统计案例展示时,要求学生用平板电脑实时生成数据散点图,同步解说数据采集方法,并演示如何用该模型预测校园食堂就餐人数。
(四)深化课堂互动的话语质量:设计"问题三阶跳"模板,基础问题(如公式套用)由学生互问互答,教师专注提出能引发认知冲突的对比性问题(如"古典概型与几何概型的适用边界"),并采用"追问链"技术,对关键回答进行3层递进追问,引导学生暴露思维过程。
(五)营造积极课堂氛围的激励机制:引入"数学银行"积分系统,将课堂参与行为货币化。如提出创新解法可获"思维币",帮助同学解决难题可获"互助券",积分可用于兑换自主作业减免或实验器材优先使用权,每周公布"财富榜"并举行小型拍卖会。

六、项目亮点

全自动:无需人工干预,一键生成完整评课报告

多维度:覆盖师生行为、教学话语、师生情绪

可解释:基于加权欧氏距离,评分科学可解释

专业化:AI 输出内容符合教研规范、可直接使用

高扩展:支持文件 / 内存流,可接入 Web 系统、接口服务

可落地:改进建议具体、可操作、可直接用于课堂优化

七、适用场景

智慧课堂分析平台

教师课堂自我诊断

学校教研、评课系统

区域教育质量监测

师范生教学技能训练

公开课、优质课自动评估

八、总结

本文实现了一套AI 驱动的全自动课堂教学质量分析系统,通过多维度数据评分 + 大模型智能诊断,让课堂评估从 "人工主观" 走向 "数据客观"。

系统可快速输出课堂得分、问题分析、改进建议,大幅提升教研效率,降低评课成本,是智慧教育领域非常实用的技术工具。

相关推荐
rosemary5123 小时前
AI Infra 后端开发工程师 — 学习路线
人工智能·学习
oy_mail3 小时前
当前主流大语言模型核心优势解析:Gemini、GPT与Claude的能力图谱
人工智能·媒体
极客老王说Agent3 小时前
【企业级Agent】制造业生产预算智能管控系统使用教程:2026企业数智化转型全实战
人工智能·ai·chatgpt
曾响铃3 小时前
堆卡时代终结:AI算力基础设施迎来“系统重构”时刻
人工智能·重构
互联圈运营观察3 小时前
打造半导体产线“数字安全屏障”:极光私有化方案护航高端制造
人工智能
深圳市九鼎创展科技3 小时前
九鼎创展 X7110 开发板(JH7110):国产 RISC-V 多媒体平台全解析
大数据·linux·人工智能·嵌入式硬件·ubuntu·risc-v
ECT-OS-JiuHuaShan3 小时前
存在是微分张量积,标量是参数但不可能是本质。还原论泛化,是语义劫持和以偏概全的逻辑谋杀伪科学庞氏骗局
数据库·人工智能·算法·机器学习·数学建模
AI人工智能+3 小时前
基于高精度OCR与大模型融合的智能文档抽取系统,著提升政务服务效率,推动从“自动化“向“智能化“转型
深度学习·语言模型·ocr·文档抽取
跨境猫小妹3 小时前
邮政与燃油附加同步抬升之后跨境卖家如何预留尾程成本缓冲
大数据·人工智能·产品运营·跨境电商·营销策略