统计学习方法笔记_cbr:第九章 EM算法及其推广

第九章 EM算法及其推广

9.1 EM算法的引入

概率模型有时含有观测变量,又含有隐变量或潜在变量。
EM算法就是含有隐变量概率模型参数的极大似然估计法;
EM算法是一种迭代算法,算法的迭代由两步组成:
E步:求期望;
M步:求极大;

9.1.1 EM算法
EM算法与初值有关,不同的初值可能对应不同的参数值;
观测随机变量的数据和隐随机变量的数据称为完全数据;
单单只有观测随机变量的数据叫不完全数据;
EM算法通过迭代求 模型的极大似然估计;
在这里插入图片描述
Q函数怎么来的呢?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
=>下界B函数=Q函数
同过使下界增大来使得对数似然函数L(θ)增大;
缺点:如图下界极大值点对应的并非是L(θ)的极大值;所以EM算法并不能保证找打全局最优解;
在这里插入图片描述

9.2 EM算法的收敛性

定理 9.1
P(Y|θ(i))单调递增;
定理 9.2
(1). 如果P(Y|θ(i))有界,L(θ(i)))收敛;
(2).Q函数,L函数 满足一定条件下,θ(i)的收敛值是L(θ)的稳定点;

L(θ(i)))收敛和θ(i)的收敛是两层意思;

9.3 EM算法在高斯混合模型学习中的应用

用EM算法求高斯混合模型参数;

9.3.1 高斯混合模型

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

在这里插入图片描述

在这里插入图片描述

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

9.4 EM算法的推广

9.4.1 F函数的极大-极大算法;

在这里插入图片描述

F函数固定一个θi极大P,再在P的基础上极大化θi+1;
最后都是以相同的Q函数得出相同的θ序列收尾;

9.4.2 GEM 算法

在这里插入图片描述

有时求Q函数极大化很难;
GEM算法就一次改变θ中的一个分量求最大化,d次最大化(遍历θ所有参数)后即可求出使Q函数最大化的θ;

<think>我们正在处理串口接收的结构化文本数据,需要从中提取特定的字段(branch,V,Mode,L_Pump,R_Pump)并生成CSV文件。假设串口数据是以文本行形式接收的,每行包含多个字段,字段之间可能有空格、逗号或冒号等分隔符。步骤:1.从串口读取数据(这里我们可以模拟从文件读取,实际串口读取需要用到串口库,如Windows的CreateFile/ReadFile,或者第三方库如boost::asio)2.解析每一行,提取所需字段3.将提取的字段写入CSV文件由于我们无法直接操作串口,这里假设数据已经保存在文本文件中(例如serial_data.txt),我们从这个文件读取。数据示例(模拟串口接收的数据): [2023-11-1514:30:45] branch:mainV:2.5.1 Mode:AutoL_Pump:ON R_Pump:OFF[2023-11-1514:31:20] branch:devV:2.5.2 Mode:ManualL_Pump:OFF R_Pump:ON我们需要提取:-时间戳(在方括号内)作为一列,但问题中要求的是branch, V, Mode, L_Pump, R_Pump,注意示例中还有branch和Mode。根据问题描述,需要提取的字段是:branch, V, Mode, L_Pump, R_Pump。但是注意,在示例数据中,branch出现在时间戳后面的部分,而时间戳我们并没有要求提取。所以我们需要提取的是:branch:后面的值V:后面的值Mode:后面的值L_Pump:后面的值R_Pump:后面的值因此,我们调整正则表达式来匹配这些字段。由于问题中提到了“结构化文本协议”,我们假设字段的顺序可能变化,但字段名是固定的。计划使用正则表达式来匹配每个字段,因为字段位置不固定,且用字段名作为关键字。注意:字段名不区分大小写?但为了安全,我们可以使用不区分大小写的匹配。步骤:1.打开输入文件(模拟串口数据)2.打开输出CSV文件3.写入CSV表头:branch,V,Mode,L_Pump,R_Pump4.逐行处理:使用正则表达式匹配每个字段的值如果匹配到,则提取;否则,该字段为空(或者跳过整行?根据需求)5.将匹配到的字段写入CSV的一行正则表达式设计(不区分大小写):branch:模式:`branch:\s*(\S+)`V:模式:`V:\s*(\S+)`注意:V后面有冒号,且可能紧接着空格Mode:模式:`Mode:\s*(\S+)`L_Pump:模式:`L_Pump:\s*(\S+)`R_Pump:模式:`R_Pump:\s*(\S+)`注意:\S+匹配非空白字符,直到遇到空格或行尾。但是,如果字段值包含空格怎么办?例如,branch可能是"mainbranch"。因此,我们可以考虑匹配直到下一个字段开始(即下一个关键字或行尾)的内容。但这样比较复杂。另一种方法是:字段值可能被引号包围?但示例中没有。所以暂时按非空白字符处理。如果字段值包含空格,那么上述模式将只匹配到第一个空格前的部分。所以我们需要更精确的匹配。考虑到字段之间是用空格分隔的,且字段名后面是冒号,我们可以这样:匹配直到下一个字段开始(即下一个关键字)或行尾。我们可以使用:`branch:\s*([^ \r\n]*)`?但这样还是不能匹配带空格的。所以改为:匹配直到下一个关键字或行尾。使用负向预测:`branch:\s*((?:(?!\b(?:branch|V|Mode|L_Pump|R_Pump):).)*)`这样太复杂,且效率低。考虑到实际应用,字段值通常不会包含空格,或者我们可以接受用非空格字符?所以先按非空格处理,如果遇到问题再调整。因此,我们先用`\S+`,如果发现数据中有空格,再调整。另外,时间戳部分我们不需要,所以不用提取。代码结构:注意:如果一行中缺少某些字段,我们可能希望跳过该行,或者用空值填充。这里根据需求,我们可以选择跳过缺失关键字段的行。我们要求每个字段都必须存在,才输出一行。代码实现:由于我们使用正则表达式,需要包含<regex>头文件。注意:如果使用C++11及以上,正则表达式支持是标准的。我们假设编译环境支持C++11。代码:注意:实际串口通信中,数据可能是分段到达的。但这里我们假设数据已经按行组织好了(每行以换行符结束)。因此,我们按行读取文件,然后逐行解析。如果实际串口读取,需要处理缓冲区,并分割行。这里我们只处理已经按行存储的文件。下面给出完整代码:</think># C++ 解析串口通信结构化文本协议并生成CSV 下面是一个完整的C++解决方案,用于解析串口通信中的结构化文本协议,提取branch、V、Mode、L_Pump、R_Pump等字段,并生成CSV格式输出。 ```cpp #include <iostream> #include <fstream> #include <sstream> #include <string> #include <vector> #include <regex> #include <windows.h> // 用于串口通信 // 串口通信配置 constexpr const char* PORT_NAME = "COM3"; // 串口号 constexpr DWORD BAUD_RATE = CBR_9600; // 波特率 constexpr DWORD BUFFER_SIZE = 1024; // 接收缓冲区大小 // 打开串口 HANDLE OpenSerialPort(const char* portName) { HANDLE hSerial = CreateFileA( portName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (hSerial == INVALID_HANDLE_VALUE) { std::cerr << "错误:无法打开串口 " << portName << std::endl; return INVALID_HANDLE_VALUE; } // 配置串口参数 DCB dcbSerialParams = {0}; dcbSerialParams.DCBlength = sizeof(dcbSerialParams); if (!GetCommState(hSerial, &dcbSerialParams)) { std::cerr << "错误:无法获取串口状态" << std::endl; CloseHandle(hSerial); return INVALID_HANDLE_VALUE; } dcbSerialParams.BaudRate = BAUD_RATE; dcbSerialParams.ByteSize = 8; dcbSerialParams.StopBits = ONESTOPBIT; dcbSerialParams.Parity = NOPARITY; if (!SetCommState(hSerial, &dcbSerialParams)) { std::cerr << "错误:无法设置串口参数" << std::endl; CloseHandle(hSerial); return INVALID_HANDLE_VALUE; } // 配置超时 COMMTIMEOUTS timeouts = {0}; timeouts.ReadIntervalTimeout = 50; timeouts.ReadTotalTimeoutConstant = 50; timeouts.ReadTotalTimeoutMultiplier = 10; timeouts.WriteTotalTimeoutConstant = 50; timeouts.WriteTotalTimeoutMultiplier = 10; if (!SetCommTimeouts(hSerial, &timeouts)) { std::cerr << "错误:无法设置串口超时" << std::endl; CloseHandle(hSerial); return INVALID_HANDLE_VALUE; } return hSerial; } // 解析数据行并提取字段 bool ParseDataLine(const std::string& line, std::string& branch, std::string& version, std::string& mode, std::string& lPump, std::string& rPump) { // 使用正则表达式匹配字段 std::regex branchPattern(R"(branch:\s*([^\s,]+))", std::regex::icase); std::regex versionPattern(R"(V:\s*([^\s,]+))", std::regex::icase); std::regex modePattern(R"(Mode:\s*([^\s,]+))", std::regex::icase); std::regex lPumpPattern(R"(L_Pump:\s*(\w+))", std::regex::icase); std::regex rPumpPattern(R"(R_Pump:\s*(\w+))", std::regex::icase); std::smatch match; bool found = true; // 提取branch if (std::regex_search(line, match, branchPattern) && match.size() > 1) { branch = match[1]; } else { branch = "N/A"; found = false; } // 提取V if (std::regex_search(line, match, versionPattern) && match.size() > 1) { version = match[1]; } else { version = "N/A"; found = false; } // 提取Mode if (std::regex_search(line, match, modePattern) && match.size() > 1) { mode = match[1]; } else { mode = "N/A"; found = false; } // 提取L_Pump if (std::regex_search(line, match, lPumpPattern) && match.size() > 1) { lPump = match[1]; } else { lPump = "N/A"; found = false; } // 提取R_Pump if (std::regex_search(line, match, rPumpPattern) && match.size() > 1) { rPump = match[1]; } else { rPump = "N/A"; found = false; } return found; } int main() { // 打开串口 HANDLE hSerial = OpenSerialPort(PORT_NAME); if (hSerial == INVALID_HANDLE_VALUE) { return 1; } // 创建输出CSV文件 std::ofstream outFile("serial_output.csv"); if (!outFile.is_open()) { std::cerr << "错误:无法创建输出文件" << std::endl; CloseHandle(hSerial); return 1; } // 写入CSV表头 outFile << "Branch,Version,Mode,L_Pump,R_Pump\n"; // 读取串口数据 char buffer[BUFFER_SIZE]; DWORD bytesRead; std::string partialLine; // 用于处理跨缓冲区的行 std::cout << "开始接收串口数据..." << std::endl; std::cout << "按Ctrl+C停止程序" << std::endl; while (true) { if (!ReadFile(hSerial, buffer, BUFFER_SIZE - 1, &bytesRead, NULL)) { std::cerr << "错误:读取串口失败" << std::endl; break; } if (bytesRead > 0) { buffer[bytesRead] = '\0'; // 确保字符串终止 std::string data(buffer); // 将部分行与新数据合并 std::string fullData = partialLine + data; partialLine.clear(); // 清空部分行缓存 // 分割为行 std::istringstream stream(fullData); std::string line; std::vector<std::string> lines; while (std::getline(stream, line)) { // 检查是否行不完整(缺少换行符) if (!stream.eof() || line.empty()) { lines.push_back(line); } else { // 保存不完整的行以便下次处理 partialLine = line; } } // 处理每一行 for (const auto& line : lines) { std::string branch, version, mode, lPump, rPump; if (ParseDataLine(line, branch, version, mode, lPump, rPump)) { // 写入CSV outFile << branch << "," << version << "," << mode << "," << lPump << "," << rPump << "\n"; // 实时显示处理结果 std::cout << "解析成功: " << line << std::endl; std::cout << " Branch: " << branch << ", V: " << version << ", Mode: " << mode << ", L_Pump: " << lPump << ", R_Pump: " << rPump << std::endl; } else { std::cerr << "警告:无法解析行: " << line << std::endl; } } // 刷新输出以确保数据写入文件 outFile.flush(); } } // 清理资源 CloseHandle(hSerial); outFile.close(); std::cout << "程序结束" << std::endl; return 0; } ``` ## 解决方案说明 ### 1. 串口通信配置 - 使用Windows API (`CreateFile`, `ReadFile`)进行串口通信 - 可配置串口号(COM3)、波特率(9600)和缓冲区大小 - 设置串口参数(数据位、停止位、奇偶校验) - 配置超时参数以确保高效读取 ### 2. 数据结构解析 使用正则表达式解析结构化文本协议: ```cpp std::regex branchPattern(R"(branch:\s*([^\s,]+))", std::regex::icase); std::regex versionPattern(R"(V:\s*([^\s,]+))", std::regex::icase); // 其他字段类似... ``` ### 3. 数据处理流程 1. 打开串口并配置参数 2. 创建输出CSV文件并写入表头 3. 循环读取串口数据: - 处理不完整行(跨缓冲区的情况) - 分割数据为独立行 - 解析每行数据提取所需字段 - 将有效数据写入CSV文件 - 实时显示处理结果 4. 关闭资源并退出 ### 4. 错误处理 - 串口打开失败处理 - 文件创建失败处理 - 数据解析失败警告 - 不完整数据处理 - 串口读取错误处理 ## 正则表达式设计说明 ### 1. 字段匹配模式 - **branch**: `branch:\s*([^\s,]+)` - 匹配"branch:"后的非空格、非逗号内容 - **V**: `V:\s*([^\s,]+)` - 匹配"V:"后的非空格内容 - **Mode**: `Mode:\s*([^\s,]+)` - 匹配"Mode:"后的非空格内容 - **L_Pump**: `L_Pump:\s*(\w+)` - 匹配"L_Pump:"后的单词字符 - **R_Pump**: `R_Pump:\s*(\w+)` - 匹配"R_Pump:"后的单词字符 ### 2. 特殊处理 - 使用`std::regex::icase`实现不区分大小写匹配 - 处理字段值中的空格和特殊字符 - 处理字段顺序不固定的情况 ## 使用示例 ### 输入数据示例 ``` [2023-11-15 14:30:45] branch:main V:2.5.1 Mode:Auto L_Pump:ON R_Pump:OFF [2023-11-15 14:31:20] branch:dev V:2.5.2 Mode:Manual L_Pump:OFF R_Pump:ON [2023-11-15 14:32:10] branch:test V:2.5.3 Mode:Standby L_Pump:STANDBY R_Pump:ON ``` ### 输出CSV示例 ```csv Branch,Version,Mode,L_Pump,R_Pump main,2.5.1,Auto,ON,OFF dev,2.5.2,Manual,OFF,ON test,2.5.3,Standby,STANDBY,ON ``` ## 性能优化建议 1. **双缓冲区技术**: ```cpp char buffer1[BUFFER_SIZE]; char buffer2[BUFFER_SIZE]; // 使用一个缓冲区读取时,处理另一个缓冲区 ``` 2. **多线程处理**: ```cpp #include <thread> void ReadThread(HANDLE hSerial) { // 读取串口数据 } void ProcessThread() { // 处理数据 } ``` 3. **批量写入**: ```cpp std::stringstream batch; // 积累多行后批量写入文件 outFile << batch.str(); ``` 4. **内存映射文件**: ```cpp HANDLE hFile = CreateFileA(/* ... */); HANDLE hMap = CreateFileMapping(/* ... */); char* pData = (char*)MapViewOfFile(/* ... */); ``` ## 扩展功能 1. **数据验证**: ```cpp bool ValidateMode(const std::string& mode) { const std::vector<std::string> validModes = {"Auto", "Manual", "Standby"}; return std::find(validModes.begin(), validModes.end(), mode) != validModes.end(); } ``` 2. **状态变化检测**: ```cpp std::string lastLPumpState; if (lPump != lastLPumpState) { std::cout << "L_Pump状态变化: " << lastLPumpState << " -> " << lPump << std::endl; lastLPumpState = lPump; } ``` 3. **数据统计**: ```cpp struct Stats { int autoCount = 0; int manualCount = 0; int onCount = 0; int offCount = 0; }; // 更新统计信息 if (mode == "Auto") stats.autoCount++; if (lPump == "ON") stats.onCount++; ``` 4. **实时数据可视化**: ```cpp // 使用第三方库如Qt或ImGui创建实时监控界面 ``` ## 常见问题解决 1. **串口无法打开**: - 检查串口号是否正确 - 确认串口未被其他程序占用 - 以管理员权限运行程序 2. **数据解析失败**: - 检查数据格式是否匹配正则表达式 - 添加调试输出显示原始数据 - 调整正则表达式增强兼容性 3. **数据丢失问题**: - 增加缓冲区大小 - 优化读取循环频率 - 添加数据校验机制 4. **性能瓶颈**: - 使用性能分析工具定位瓶颈 - 优化正则表达式 - 减少文件写入频率
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值