# 导入PyTorch核心库
import torch
# 导入神经网络模块
import torch.nn as nn
# 导入优化器模块
import torch.optim as optim
# 导入函数式API模块
import torch.nn.functional as F
# 导入数据集和数据加载器
from torch.utils.data import Dataset, DataLoader
# 导入NumPy数值计算库
import numpy as np
# 导入matplotlib绘图库
import matplotlib.pyplot as plt
# 导入t-SNE降维算法
from sklearn.manifold import TSNE
# 导入PCA降维算法
from sklearn.decomposition import PCA
# 导入jieba中文分词库
import jieba
# 导入正则表达式库
import re
# 导入计数器和默认字典
from collections import Counter, defaultdict
# 导入进度条库
from tqdm import tqdm
# 导入序列化库
import pickle
# 导入操作系统接口
import os
# 导入随机数生成库
import random# 设置matplotlib使用中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
# 设置matplotlib正确显示负号
plt.rcParams['axes.unicode_minus'] = Falseclass TextProcessor:"""文本预处理器类 - 负责文本清理、分词、词汇表构建等功能"""def __init__(self, min_count=5, window_size=5):"""初始化文本预处理器Args:min_count: 词汇最小出现次数,低于此次数的词将被过滤window_size: 窗口大小,用于确定上下文范围"""# 设置词汇最小出现次数阈值self.min_count = min_count# 设置窗口大小self.window_size = window_size# 初始化词汇到索引的映射字典self.word2idx = {}# 初始化索引到词汇的映射字典self.idx2word = {}# 初始化词汇计数器self.word_counts = Counter()# 初始化词汇表大小self.vocab_size = 0def clean_text(self, text):"""清理文本内容Args:text: 输入的原始文本Returns:清理后的文本"""# 使用正则表达式移除特殊字符,仅保留中文、英文、数字和空格text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\s]', '', text)# 去除首尾空格并返回return text.strip()def tokenize(self, text):"""对文本进行分词处理Args:text: 输入文本Returns:分词后的词汇列表"""# 首先清理文本text = self.clean_text(text)# 使用jieba进行中文分词tokens = list(jieba.cut(text))# 过滤掉空白符和长度小于等于1的词,返回有效词汇列表return [token for token in tokens if token.strip() and len(token) > 1]def build_vocab(self, texts):"""构建词汇表Args:texts: 输入的文本列表"""# 打印词汇表构建开始提示print("正在构建词汇表...")# 遍历所有文本,统计词频for text in tqdm(texts, desc="统计词频"):# 对当前文本进行分词tokens = self.tokenize(text)# 更新词汇计数器self.word_counts.update(tokens)# 根据最小出现次数过滤词汇,构建有效词汇列表vocab_words = [word for word, count in self.word_counts.items() if count >= self.min_count]# 构建词汇到索引的映射字典self.word2idx = {word: idx for idx, word in enumerate(vocab_words)}# 构建索引到词汇的映射字典self.idx2word = {idx: word for word, idx in self.word2idx.items()}# 更新词汇表大小self.vocab_size = len(vocab_words)# 打印词汇表统计信息print(f"词汇表大小: {self.vocab_size}")print(f"总词数: {sum(self.word_counts.values())}")def text_to_indices(self, text):"""将文本转换为索引序列Args:text: 输入文本Returns:词汇索引序列"""# 对文本进行分词tokens = self.tokenize(text)# 将词汇转换为索引,忽略不在词汇表中的词return [self.word2idx[token] for token in tokens if token in self.word2idx]def generate_training_data(self, texts, model_type='cbow'):"""生成训练数据Args:texts: 输入文本列表model_type: 模型类型('cbow'或'skipgram')Returns:训练数据列表"""# 初始化训练数据列表training_data = []# 打印训练数据生成开始提示print(f"生成{model_type.upper()}训练数据...")# 遍历所有文本for text in tqdm(texts, desc="处理文本"):# 将文本转换为索引序列indices = self.text_to_indices(text)# 如果序列长度不够构建窗口,跳过此文本if len(indices) < self.window_size * 2 + 1:continue# 遍历序列中的每个可能的中心词位置for i in range(self.window_size, len(indices) - self.window_size):# 根据模型类型生成不同的训练样本if model_type == 'cbow':# CBOW模型:使用上下文词预测中心词# 构建上下文词索引(中心词前后各window_size个词)context = (indices[i-self.window_size:i] + indices[i+1:i+self.window_size+1])# 中心词作为目标target = indices[i]# 添加训练样本(上下文,中心词)training_data.append((context, target))else:# Skip-gram模型:使用中心词预测上下文词# 当前位置的词作为中心词center_word = indices[i]# 遍历窗口内的所有上下文词for j in range(i-self.window_size, i+self.window_size+1):# 跳过中心词本身if j != i:# 获取上下文词context_word = indices[j]# 添加训练样本(中心词,上下文词)training_data.append((center_word, context_word))# 打印生成的训练样本数量print(f"生成训练样本数: {len(training_data)}")# 返回训练数据return training_dataclass CBOWDataset(Dataset):"""CBOW模型数据集类 - 继承自PyTorch的Dataset类"""def __init__(self, training_data):"""初始化CBOW数据集Args:training_data: 训练数据列表"""# 存储训练数据self.training_data = training_datadef __len__(self):"""返回数据集大小Returns:数据集的样本数量"""# 返回训练数据的长度return len(self.training_data)def __getitem__(self, idx):"""获取指定索引的数据样本Args:idx: 样本索引Returns:上下文张量和目标张量"""# 获取指定索引的上下文和目标context, target = self.training_data[idx]# 将上下文转换为长整型张量,目标转换为长整型张量return torch.tensor(context, dtype=torch.long), torch.tensor(target, dtype=torch.long)class SkipGramDataset(Dataset):"""Skip-gram模型数据集类 - 继承自PyTorch的Dataset类"""def __init__(self, training_data):"""初始化Skip-gram数据集Args:training_data: 训练数据列表"""# 存储训练数据self.training_data = training_datadef __len__(self):"""返回数据集大小Returns:数据集的样本数量"""# 返回训练数据的长度return len(self.training_data)def __getitem__(self, idx):"""获取指定索引的数据样本Args:idx: 样本索引Returns:中心词张量和上下文张量"""# 获取指定索引的中心词和上下文词center, context = self.training_data[idx]# 将中心词和上下文词转换为长整型张量return torch.tensor(center, dtype=torch.long), torch.tensor(context, dtype=torch.long)class CBOWModel(nn.Module):"""CBOW模型类 - 继承自PyTorch的nn.Module"""def __init__(self, vocab_size, embedding_dim):"""初始化CBOW模型Args:vocab_size: 词汇表大小embedding_dim: 词向量维度"""# 调用父类构造函数super(CBOWModel, self).__init__()# 创建词嵌入层,将词汇索引映射为词向量self.embedding = nn.Embedding(vocab_size, embedding_dim)# 创建线性层,将词向量映射为词汇表大小的输出self.linear = nn.Linear(embedding_dim, vocab_size)def forward(self, context):"""前向传播函数Args:context: 上下文词索引张量,形状为(batch_size, context_size)Returns:输出张量,形状为(batch_size, vocab_size)"""# 通过嵌入层获取上下文词的词向量,形状为(batch_size, context_size, embedding_dim)embeddings = self.embedding(context)# 计算上下文词向量的平均值,形状为(batch_size, embedding_dim)context_mean = torch.mean(embeddings, dim=1)# 通过线性层映射到词汇表大小,形状为(batch_size, vocab_size)output = self.linear(context_mean)# 返回输出结果return outputclass SkipGramModel(nn.Module):"""Skip-gram模型类 - 继承自PyTorch的nn.Module"""def __init__(self, vocab_size, embedding_dim):"""初始化Skip-gram模型Args:vocab_size: 词汇表大小embedding_dim: 词向量维度"""# 调用父类构造函数super(SkipGramModel, self).__init__()# 创建词嵌入层,将词汇索引映射为词向量self.embedding = nn.Embedding(vocab_size, embedding_dim)# 创建线性层,将词向量映射为词汇表大小的输出self.linear = nn.Linear(embedding_dim, vocab_size)def forward(self, center_word):"""前向传播函数Args:center_word: 中心词索引张量,形状为(batch_size,)Returns:输出张量,形状为(batch_size, vocab_size)"""# 通过嵌入层获取中心词的词向量,形状为(batch_size, embedding_dim)embedded = self.embedding(center_word)# 通过线性层映射到词汇表大小,形状为(batch_size, vocab_size)output = self.linear(embedded)# 返回输出结果return outputclass Word2Vec:"""Word2Vec主类 - 封装了完整的Word2Vec模型训练和使用功能"""def __init__(self, embedding_dim=100, window_size=5, min_count=5, model_type='cbow'):"""初始化Word2Vec模型Args:embedding_dim: 词向量维度window_size: 窗口大小min_count: 词汇最小出现次数model_type: 模型类型('cbow'或'skipgram')"""# 设置词向量维度self.embedding_dim = embedding_dim# 设置窗口大小self.window_size = window_size# 设置最小词频self.min_count = min_count# 设置模型类型self.model_type = model_type# 创建文本处理器实例self.processor = TextProcessor(min_count, window_size)# 初始化模型为空self.model = None# 设置计算设备(GPU优先,否则使用CPU)self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')# 打印使用的设备信息print(f"使用设备: {self.device}")def create_sample_corpus(self):"""创建示例语料库Returns:扩展后的语料库列表"""# 定义示例语料库corpus = ["我喜欢吃苹果和香蕉","苹果是一种很好的水果","香蕉含有丰富的维生素","我每天都会吃水果","水果对健康很有益","苹果和香蕉都很美味","我喜欢在早上吃水果","健康的生活需要多吃水果","苹果香蕉都是我喜欢的水果","维生素对身体健康很重要","我喜欢看电影和听音乐","电影院里播放着精彩的电影","音乐能够让人放松心情","我每天都会听音乐","音乐对情绪很有帮助","电影和音乐都很有趣","我喜欢在晚上看电影","娱乐活动能够缓解压力","电影音乐都是我喜欢的娱乐","放松心情对身心健康很重要","学习是一件很重要的事情","我喜欢学习新的知识","知识能够让人变得更聪明","学校是学习知识的地方","老师会教给我们很多知识","学习需要认真和努力","我每天都会学习新东西","教育对个人发展很重要","学习知识是学生的职责","努力学习才能取得好成绩","运动对身体健康很重要","我喜欢跑步和游泳","跑步能够锻炼身体","游泳是一种很好的运动","运动能够增强体质","我每天都会做运动","健身房里有很多运动器材","坚持运动能够保持健康","跑步游泳都是我喜欢的运动","锻炼身体需要持之以恒"]# 初始化扩展后的语料库extended_corpus = []# 重复语料库内容10次以增加数据量for _ in range(10):# 将原始语料库添加到扩展语料库中extended_corpus.extend(corpus)# 返回扩展后的语料库return extended_corpusdef train(self, texts, epochs=100, batch_size=64, learning_rate=0.001):"""训练Word2Vec模型Args:texts: 训练文本列表epochs: 训练轮数batch_size: 批次大小learning_rate: 学习率"""# 打印训练开始提示print(f"开始训练{self.model_type.upper()}模型...")# 使用文本处理器构建词汇表self.processor.build_vocab(texts)# 生成训练数据training_data = self.processor.generate_training_data(texts, self.model_type)# 根据模型类型创建相应的数据集和模型if self.model_type == 'cbow':# 创建CBOW数据集dataset = CBOWDataset(training_data)# 创建CBOW模型并移动到指定设备self.model = CBOWModel(self.processor.vocab_size, self.embedding_dim).to(self.device)else:# 创建Skip-gram数据集dataset = SkipGramDataset(training_data)# 创建Skip-gram模型并移动到指定设备self.model = SkipGramModel(self.processor.vocab_size, self.embedding_dim).to(self.device)# 创建数据加载器dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)# 创建Adam优化器optimizer = optim.Adam(self.model.parameters(), lr=learning_rate)# 创建交叉熵损失函数criterion = nn.CrossEntropyLoss()# 初始化损失记录列表losses = []# 开始训练循环for epoch in range(epochs):# 初始化当前轮次的总损失total_loss = 0# 初始化批次计数器num_batches = 0# 创建进度条progress_bar = tqdm(dataloader, desc=f'Epoch {epoch+1}/{epochs}')# 遍历数据批次for batch in progress_bar:# 根据模型类型处理批次数据if self.model_type == 'cbow':# CBOW模型:获取上下文和目标context, target = batch# 将数据移动到指定设备context = context.to(self.device)target = target.to(self.device)# 清零梯度optimizer.zero_grad()# 前向传播output = self.model(context)# 计算损失loss = criterion(output, target)else:# Skip-gram模型:获取中心词和上下文center, context = batch# 将数据移动到指定设备center = center.to(self.device)context = context.to(self.device)# 清零梯度optimizer.zero_grad()# 前向传播output = self.model(center)# 计算损失loss = criterion(output, context)# 反向传播loss.backward()# 更新参数optimizer.step()# 累计损失total_loss += loss.item()# 增加批次计数num_batches += 1# 更新进度条显示progress_bar.set_postfix({'Loss': f'{loss.item():.4f}'})# 计算平均损失avg_loss = total_loss / num_batches# 记录损失值losses.append(avg_loss)# 每10个epoch打印一次损失if (epoch + 1) % 10 == 0:print(f'Epoch [{epoch+1}/{epochs}], Average Loss: {avg_loss:.4f}')# 绘制训练损失曲线self.plot_loss_curve(losses)# 打印训练完成提示print("训练完成!")def get_word_vector(self, word):"""获取单词的词向量Args:word: 输入单词Returns:词向量数组或None"""# 检查词汇是否在词汇表中if word not in self.processor.word2idx:return None# 获取词汇的索引word_idx = self.processor.word2idx[word]# 创建词汇索引张量并移动到指定设备word_tensor = torch.tensor([word_idx], dtype=torch.long).to(self.device)# 在不计算梯度的情况下获取词向量with torch.no_grad():# 通过嵌入层获取词向量embedding = self.model.embedding(word_tensor)# 将张量转换为numpy数组并展平return embedding.cpu().numpy().flatten()def find_similar_words(self, word, top_k=10):"""查找与给定词汇最相似的词汇Args:word: 目标词汇top_k: 返回最相似的k个词汇Returns:相似词汇列表,每个元素为(词汇, 相似度)"""# 检查词汇是否在词汇表中if word not in self.processor.word2idx:print(f"词汇 '{word}' 不在词典中")return []# 获取目标词汇的词向量target_vector = self.get_word_vector(word)# 如果获取失败,返回空列表if target_vector is None:return []# 初始化相似度列表similarities = []# 遍历词汇表中的所有词汇for w in self.processor.word2idx:# 跳过目标词汇本身if w != word:# 获取当前词汇的词向量vector = self.get_word_vector(w)# 如果成功获取词向量if vector is not None:# 计算余弦相似度similarity = np.dot(target_vector, vector) / (np.linalg.norm(target_vector) * np.linalg.norm(vector))# 添加到相似度列表similarities.append((w, similarity))# 按相似度降序排序similarities.sort(key=lambda x: x[1], reverse=True)# 返回前top_k个最相似的词汇return similarities[:top_k]def word_analogy(self, word1, word2, word3, top_k=5):"""词汇类比功能 (word1 - word2 + word3 = ?)Args:word1: 第一个词汇word2: 第二个词汇word3: 第三个词汇top_k: 返回最相似的k个结果Returns:类比结果列表,每个元素为(词汇, 相似度)"""# 将三个词汇组成列表words = [word1, word2, word3]# 初始化词向量列表vectors = []# 遍历三个词汇for word in words:# 检查词汇是否在词汇表中if word not in self.processor.word2idx:print(f"词汇 '{word}' 不在词典中")return []# 获取词汇的词向量vector = self.get_word_vector(word)# 如果获取失败,返回空列表if vector is None:return []# 添加到词向量列表vectors.append(vector)# 计算类比向量 (word1 - word2 + word3)target_vector = vectors[0] - vectors[1] + vectors[2]# 初始化相似度列表similarities = []# 遍历词汇表中的所有词汇for w in self.processor.word2idx:# 跳过输入的三个词汇if w not in words:# 获取当前词汇的词向量vector = self.get_word_vector(w)# 如果成功获取词向量if vector is not None:# 计算与目标向量的余弦相似度similarity = np.dot(target_vector, vector) / (np.linalg.norm(target_vector) * np.linalg.norm(vector))# 添加到相似度列表similarities.append((w, similarity))# 按相似度降序排序similarities.sort(key=lambda x: x[1], reverse=True)# 返回前top_k个最相似的词汇return similarities[:top_k]def visualize_embeddings(self, words=None, method='tsne'):"""可视化词向量Args:words: 要可视化的词汇列表,如果为None则使用最频繁的词汇method: 降维方法('tsne'或'pca')"""# 如果没有指定词汇,使用最频繁的50个词汇if words is None:words = [word for word, _ in self.processor.word_counts.most_common(50)]# 初始化词向量列表和有效词汇列表vectors = []valid_words = []# 遍历指定的词汇for word in words:# 获取词汇的词向量vector = self.get_word_vector(word)# 如果成功获取词向量if vector is not None:# 添加到词向量列表vectors.append(vector)# 添加到有效词汇列表valid_words.append(word)# 如果没有可用的词向量,返回if len(vectors) == 0:print("没有可用的词向量")return# 将词向量列表转换为numpy数组vectors = np.array(vectors)# 根据指定方法进行降维if method == 'tsne':# 使用t-SNE降维到2维reducer = TSNE(n_components=2, random_state=42)vectors_2d = reducer.fit_transform(vectors)else:# 使用PCA降维到2维reducer = PCA(n_components=2, random_state=42)vectors_2d = reducer.fit_transform(vectors)# 创建图形plt.figure(figsize=(12, 8))# 绘制散点图plt.scatter(vectors_2d[:, 0], vectors_2d[:, 1], alpha=0.7)# 为每个点添加词汇标签for i, word in enumerate(valid_words):plt.annotate(word, (vectors_2d[i, 0], vectors_2d[i, 1]), xytext=(5, 5), textcoords='offset points')# 设置图形标题plt.title(f'词向量可视化 ({method.upper()})')# 设置x轴标签plt.xlabel('维度 1')# 设置y轴标签plt.ylabel('维度 2')# 显示网格plt.grid(True, alpha=0.3)# 调整布局plt.tight_layout()# 保存图形plt.savefig(f'word_embeddings_{method}.png', dpi=300, bbox_inches='tight')# 显示图形plt.show()def plot_loss_curve(self, losses):"""绘制训练损失曲线Args:losses: 损失值列表"""# 创建图形plt.figure(figsize=(10, 6))# 绘制损失曲线plt.plot(losses)# 设置图形标题plt.title('训练损失曲线')# 设置x轴标签plt.xlabel('Epoch')# 设置y轴标签plt.ylabel('Loss')# 显示网格plt.grid(True, alpha=0.3)# 调整布局plt.tight_layout()# 保存图形plt.savefig('training_loss.png', dpi=300, bbox_inches='tight')# 显示图形plt.show()def save_model(self, filepath):"""保存模型到文件Args:filepath: 保存路径"""# 使用torch.save保存模型状态字典和相关信息torch.save({'model_state_dict': self.model.state_dict(), # 模型参数'processor': self.processor, # 文本处理器'model_type': self.model_type, # 模型类型'embedding_dim': self.embedding_dim # 词向量维度}, filepath)# 打印保存成功提示print(f"模型已保存到 {filepath}")def load_model(self, filepath):"""从文件加载模型Args:filepath: 模型文件路径"""# 加载模型检查点checkpoint = torch.load(filepath, map_location=self.device)# 恢复文本处理器self.processor = checkpoint['processor']# 恢复模型类型self.model_type = checkpoint['model_type']# 恢复词向量维度self.embedding_dim = checkpoint['embedding_dim']# 根据模型类型创建相应的模型if self.model_type == 'cbow':# 创建CBOW模型并移动到指定设备self.model = CBOWModel(self.processor.vocab_size, self.embedding_dim).to(self.device)else:# 创建Skip-gram模型并移动到指定设备self.model = SkipGramModel(self.processor.vocab_size, self.embedding_dim).to(self.device)# 加载模型参数self.model.load_state_dict(checkpoint['model_state_dict'])# 打印加载成功提示print(f"模型已从 {filepath} 加载")def main():"""主函数 - 演示Word2Vec模型的完整使用流程"""# 打印程序标题print("=== Word2Vec 词向量模型 ===\n")# 第一部分:训练CBOW模型print("1. 训练CBOW模型")# 创建CBOW模型实例cbow_model = Word2Vec(embedding_dim=100, # 词向量维度为100window_size=5, # 窗口大小为5min_count=2, # 最小词频为2model_type='cbow' # 模型类型为CBOW)# 创建示例语料库corpus = cbow_model.create_sample_corpus()# 打印语料库统计信息print(f"语料库大小: {len(corpus)} 个句子")# 训练CBOW模型cbow_model.train(corpus, epochs=50, batch_size=32, learning_rate=0.001)# 第二部分:测试CBOW模型功能print("\n=== CBOW模型测试 ===")# 定义测试词汇列表test_words = ['苹果', '香蕉', '水果', '学习', '知识', '运动', '健康']# 遍历测试词汇,检查词向量for word in test_words:# 获取词汇的词向量vector = cbow_model.get_word_vector(word)# 检查是否成功获取词向量if vector is not None:print(f"'{word}' 的词向量维度: {vector.shape}")else:print(f"'{word}' 不在词典中")# 第三部分:测试相似词查找功能print("\n=== 相似词查找 ===")# 查找与"苹果"最相似的5个词汇similar_words = cbow_model.find_similar_words('苹果', top_k=5)print(f"与 '苹果' 最相似的词:")# 打印相似词及其相似度for word, similarity in similar_words:print(f" {word}: {similarity:.4f}")# 第四部分:测试词汇类比功能print("\n=== 词汇类比 ===")# 执行词汇类比:苹果 - 水果 + 音乐 = ?analogy_result = cbow_model.word_analogy('苹果', '水果', '音乐', top_k=3)print("苹果 - 水果 + 音乐 = ?")# 打印类比结果for word, similarity in analogy_result:print(f" {word}: {similarity:.4f}")# 第五部分:可视化词向量print("\n=== 词向量可视化 ===")# 使用t-SNE方法可视化词向量cbow_model.visualize_embeddings(method='tsne')# 保存CBOW模型cbow_model.save_model('word2vec_cbow_model.pth')# 分隔线print("\n" + "="*50)print("2. 训练Skip-gram模型")# 第六部分:训练Skip-gram模型skipgram_model = Word2Vec(embedding_dim=100, # 词向量维度为100window_size=5, # 窗口大小为5min_count=2, # 最小词频为2model_type='skipgram' # 模型类型为Skip-gram)# 训练Skip-gram模型skipgram_model.train(corpus, epochs=50, batch_size=32, learning_rate=0.001)# 第七部分:测试Skip-gram模型print("\n=== Skip-gram模型测试 ===")# 查找与"苹果"最相似的5个词汇similar_words_sg = skipgram_model.find_similar_words('苹果', top_k=5)print(f"Skip-gram模型中与 '苹果' 最相似的词:")# 打印相似词及其相似度for word, similarity in similar_words_sg:print(f" {word}: {similarity:.4f}")# 保存Skip-gram模型skipgram_model.save_model('word2vec_skipgram_model.pth')# 第八部分:模型对比说明print("\n=== 模型对比 ===")print("CBOW和Skip-gram模型训练完成!")print("- CBOW模型更适合小数据集,训练速度快")print("- Skip-gram模型更适合大数据集,对罕见词效果好")# 第九部分:交互式词向量查询print("\n=== 交互式词向量查询 ===")print("输入词汇查看相似词(输入'quit'退出):")# 开始交互式查询循环while True:# 获取用户输入user_input = input("\n请输入词汇: ")# 检查是否退出if user_input.lower() == 'quit':break# 如果输入非空if user_input.strip():# 使用CBOW模型查找相似词print(f"\nCBOW模型结果:")similar_cbow = cbow_model.find_similar_words(user_input, top_k=5)# 打印CBOW模型结果if similar_cbow:for word, sim in similar_cbow:print(f" {word}: {sim:.4f}")else:print(" 词汇不在词典中")# 使用Skip-gram模型查找相似词print(f"\nSkip-gram模型结果:")similar_sg = skipgram_model.find_similar_words(user_input, top_k=5)# 打印Skip-gram模型结果if similar_sg:for word, sim in similar_sg:print(f" {word}: {sim:.4f}")else:print(" 词汇不在词典中")# 程序入口点
if __name__ == "__main__":# 调用主函数main()

