3.2 电影网站用户性别测试_第1页
3.2 电影网站用户性别测试_第2页
3.2 电影网站用户性别测试_第3页
3.2 电影网站用户性别测试_第4页
3.2 电影网站用户性别测试_第5页
已阅读5页,还剩39页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

电影网站用户性别测试主讲:李强任务描述M网站是一个深受用户欢迎的电影社区网站,它提供大量的电影介绍及评论,包括上映影片的影讯查询及购票服务。用户可以记录想看、在看和看过的电影,顺便打分、写影评。为了提高用户的使用体验与满意度,网站计划为广大用户提供更精准更个性化的电影推荐服务。可能会使用以下用户信息数据,如表3-2所示,有部分记录的性别数据是缺失的。众所周知,性别是用户的一个非常重要的特征,也是建立推荐模型的一个重要维度指标。那么获得用户信息中缺失的性别数据,就是目前要解决的首要任务。本小节也将围绕这一主题展开,尝试以预测的方式来获得用户的性别信息。表3-2M电影网站用户信息UserIDGenderAgeOccupationZip-code1F110480672M5616700723

2515551174

357024605M2520554556F509551177M35106810任务分析如图3-21所示的数据,是对每个用户的性别信息及该用户看过的电影类型的统计情况。其中UserID代表的是用户ID,Gender代表的是用户性别,其中1代表的是女性,0代表的是男性。Age代表的是用户的年龄,Occupation代表的是用户的职业,Zip-code代表的是用户的地区编码。Action到Western代表的是电影的不同类型。例如,某条记录中Action字段的值是4,则说明该用户看过4部动作片的电影。图3-21用户观看过的电影类型统计数据任务分析本小节将使用MapReduce编程技术,利用KNN算法对已知性别用户观看的电影类型统计数据建立聚类器,并且对这个聚类器的聚类结果进行评价,选出聚类性能最好的一个聚类器用于对未知性别的用户进行聚类。因为用户在访问网站的电影时产生了大量的历史浏览数据,从用户浏览过的电影类型记录来预测该用户的性别,这可以作为一个解决思路来进行尝试,大致的实现步骤可以是这样的。(1)对用户看过的所有电影类型进行统计,再通过已知性别用户观看电影的类型统计数据建立一个聚类器。(2)向聚类器输入未知性别用户观看电影的类型统计数据,获得该用户的性别聚类。3.2.1获取数据获取数据本小节为读者提供四份与用户信息相关的文件,分别为用户对电影的评分数据ratings.dat,已知性别的用户信息数据users.dat,电影信息数据movies.dat以及数据相关字段的解释文件README,其中README文件仅作为了解数据作用,此处不做展示。部分用户对电影的评分数据ratings.dat包含4个字段,即UserID(用户ID),MovieID(电影ID),Rating(评分)及Timestamp(时间戳)。其中UserID的范围是1到6040,MovieIDS的范围是1到3952,Rating采用五分好评制度,即最高分为5分,最低分为1分。部分已知性别的用户信息数据users.dat数据包含5个字段,分别为UserID(用户ID),Gender(性别),Age(年龄),Occupation(职业)以及Zip-code(编码)。其中Occupation字段代表的是21种不同的职业类型,Age字段记录的也并不是用户的实际年龄,而是一个年龄段,比如1代表的是18岁以下,具体的解释请参考README。部分电影信息数据movies.dat字段包含MovieID(电影ID),Title(电影名称),Genres(电影类型)三个字段。其中Title字段不仅记录电影的名称,还记录了电影的上映时间。数据中总共记录了18种电影类型,包括喜剧片,动作片,警匪片,爱情片等,具体的电影类型种类请参见README。3.2.2数据变换数据变换参考任务3.1,创建名为“Movie_knn”的Maven工程,在pom.xml文件里“</properties>”与“</project>”之间添加Hadoop依赖,内容如表3-1所示。在src/main文件夹单击右键选择“New”→“Directory”,如图3-22所示,创建一个名为“datajoin”的文件夹。图3-22创建文件夹数据变换创建完文件夹后,对着“datajoin”单击右键选择“MarkDirectoryas”→“SourcesRoot”,如图3-23所示,激活“datajoin”文件夹,使其以及子文件夹下的文件成为IDEA可编译的原文件,激活后文件夹呈蓝色,然后参考3.1.4小节,创建名为“demo01”的Package。图3-23激活文件夹数据变换数据变换是指将数据从一种表现形式变为另一种表现形式的过程,数据变换主要是找到数据的特征表示。将网站用户的用户信息数据及观影记录数据转换得到用户观看电影的类型统计数据的思路如下。(1)根据UserID字段连接ratings.dat数据和users.dat数据,连接结果得到一份包含UserID(用户ID),Gender(性别),Age(年龄),Occupation(职业),Zip-code(编码),MovieID(电影ID)的数据,连接的MapReduce代码如代码3-1所示。代码3-1连接ratings.dat数据和users.dat数据的三个Java代码的重要部分publicclassJarUtil{publicstaticStringjar(Class<?>cls){//验证okStringoutputJar=cls.getName()+".jar";Stringinput=cls.getClassLoader().getResource("").getFile();input=input.substring(0,input.length()-1);input=input.substring(0,input.lastIndexOf("/")+1);input=input+"bin/";jar(input,outputJar);returnoutputJar;}....省略....数据变换编写好3个代码后,单击页面上方菜单“File”选项,选择“ProjectStructure…”,在跳出的“ProjectStructure”窗口选择“Artifacts”→“+”→“JAR”→“Frommoduleswithdependencies…”,如图3-24所示,创建Jar包。图3-24创建Jar包数据变换在弹出的对话框的“MainClass”选区中选择RatingsAndUsers文件,单击“OK”。为方便区分,将“Name”修改为“demo01”,如图3-25所示,单击“Apply”“OK”,完成Jar的添加。图3-25修改“Name”数据变换在IDEA主界面选择“Build”→“BuildArtifacts…”,如图3-26所示,在弹出的窗口中选择“demo01”→“Build”,如图3-27所示,进行编译Jar文件。图3-27编译Jar文件2图3-26编译Jar文件1数据变换为方便区分,对生成的“Movie_knn.jar”文件单击右键选择“Refactor”→“Rename…”,如图3-28所示,重命名为“demo01.jar”,如图3-29所示,然后单击“Refactor”。图3-29重命名2图3-28重命名1数据变换参考项目1将Jar包上传到Linux的/opt目录下,在HDFS上新建文件夹/movie,将ratings.dat,users.dat上传到/movie下,程序运行结果保存在/movie/ratings_users目录下。在集群终端的命令行执行如代码3-2所示的命令。代码3-2连接ratings.dat数据和users.dat数据的程序运行命令hadoopjar/opt/demo01.jardemo01.RatingsAndUsers/movie/users.dat/movie/ratings.dat/movie/ratings_users程序运行成功后登陆HDFSWebUI,选择“Utilitles…”→“Browsethefilesystem”,查看/movie/ratings_users/part-m-00000文件,单击“Headthefile”查看文件内容,如图3-30所示。图3-30ratings.dat数据和users.dat数据的连接结果数据变换(2)根据MovieID连接movies.dat数据和/movie/ratings_users/part-m-00000上的数据,连接结果得到一份包含UserID(用户ID),Gender(性别),Age(年龄),Occupation(职业),Zip-code(编码),MovieID(电影ID),Genres(电影类型)。在datajoin文件夹下创建“demo02”的Package,连接MapReduce程序代码如代码3-3所示。代码3-3连接movies.dat数据与ratings_users数据的程序代码重要部分publicclassUsersMoviesMapperextendsMapper<LongWritable,Text,Text,NullWritable>{privateHashMap<String,String>movie_info=newHashMap<String,String>();privateStringsplitter="";privateStringmovie_secondPart="";@Overrideprotectedvoidsetup(Mapper<LongWritable,Text,Text,NullWritable>.Contextcontext)throwsIOException,InterruptedException{Path[]DistributePaths=DistributedCache.getLocalCacheFiles(context.getConfiguration());splitter=context.getConfiguration().get("SPLITTER");Stringline="";BufferedReaderbr=null;....省略....数据变换参考第1步的操作,将demo02.jar包上传至Linux的opt目录下,将movies.dat数据上传到HDFS的/movie目录下,运行结果保存在/movie/users_movies。在集群终端的命令行运行如代码3-4所示的命令。代码3-4连接movies.dat数据与ratings_users数据的程序运行命令hadoopjar/opt/demo02.jardemo02.UsersAndMovies/movie/movies.dat/movie/ratings_users/part-m-00000/movie/users_movies参考第1步的操作,查看/movie/users_movies/part-m-00000文件内容,如图3-31所示。图3-31movies.dat数据与ratings_users数据连接结果数据变换(3)对每个用户看过的电影类型进行统计,例如,假设图3-31所示的数据就是UserID为1的用户看过的所有电影,那么通过统计可以得到该用户看过2部Action类电影,2部Adventure类电影,2部Animation类电影,2部Children’s类电影,3部Comedy类电影,4部Drama类电影,2部Musical类电影,2部Romance类电影,其他类型的电影都没有看过,即为0,因此可以得到该用户的一个特征向量,如表3-3所示。表3-3对某用户看过的电影类型统计得到的特征向量数据1,F,1,10,48067,2,2,2,2,3,0,0,4,0,0,0,2,0,2,0,0,0,0数据变换对每个用户看过电影类型进行统计,得到一个特征向量矩阵,可通过MapReduce编程实现。在Map阶段,对Gender(性别)做一步转换,如果是女性(F)则用1标记,如果是男性(M)则用0标记,Map输出的键是UserID(用户ID),Gender(性别),Age(年龄),Occupation(职业)和Zip-code(编码),输出的值是Genres(电影类型)。Map端的实现流程如图3-32所示。表3-3对某用户看过的电影类型统计得到的特征向量数据数据变换在Reduce阶段,reduce函数首先为每个用户初始化一个HashMap集合,集合中有18个键值对,键分别为18种电影类型,每个键对应的值为0。Map端输出的键值对中相同键的值被整合到一个列表中,reduce函数针对相同的键,遍历其值列表,对列表中每个元素根据分隔符“|”进行分割,遍历分割结果,如果HashMap集合中的键包含分割结果中的元素,则该键对应的值加1。最后将HashMap所有键对应的值及Reduce输入的键用逗号分隔符合并成一个字符串作为Reduce输出的键,Reduce输出的值则为空。Reduce端的实现流程如图3-33所示。图3-33Reduce端的实现流程数据变换Mapper类及Reducer类的实现代码如代码3-5所示。代码3-5对每个用户看过电影类型进行统计的Mapper类及Reducer代码重要部分packagedemo03;publicclassMoviesGenresMapperextendsMapper<LongWritable,Text,UserAndGender,Text>{ privateUserAndGenderuser_gender=newUserAndGender(); privateStringsplitter=""; privateTextgenres=newText(); @Override protectedvoidsetup(Mapper<LongWritable,Text,UserAndGender,Text>.Contextcontext) throwsIOException,InterruptedException{ splitter=context.getConfiguration().get("SPLITTER"); } @Override protectedvoidmap(LongWritablekey,Textvalue,Mapper<LongWritable,Text,UserAndGender,Text>.Contextcontext) throwsIOException,InterruptedException{ String[]val=value.toString().split(splitter); user_gender.setUserID(val[0]);.......省略......数据变换Driver类实现代码如代码3-6所示。代码3-6对每个用户看过电影类型进行统计的Driver类代码的重要部分packagedemo03;publicclassUserAndGenderimplementsWritableComparable<UserAndGender>{privateStringuserID;privateintgender;privateintage;privateStringoccupation;privateStringzip_code;publicintgetAge(){returnage;}publicvoidsetAge(intage){this.age=age;}.......省略......数据变换同样将代码3-5和代码3-6打包成demo03.jar,将其上传至Linux的opt目录下。在集群终端的命令行运行如代码3-7所示的命令。代码3-7对每个用户看过电影类型进行统计的程序运行命令hadoopjar/opt/demo03.jardemo03.MoviesGenres/movie/users_movies/part-m-00000/movie/gender_genre::查看/movie/gender_genre/part-m-00000文件内容,统计结果如图3-34所示。图3-34对每个用户看过电影类型进行统计的结果3.2.3数据清洗数据清洗数据清洗是数据预处理的一个重要步骤,3.2.2变换得到的数据中可能含有噪声数据,例如缺失值或者异常值,这类数据将会影响聚类器的建立,因此需要对这类数据进行处理。一般情况下,数据中的缺失值可能是表示成空(即没有值)、“NULL”“null”或“NAN”。而异常值则需要根据实际情况判断,如3.2.2集成的数据的有些属性列代表用户看过的某种类型的电影的部数,那么该值应该大于或等于0,如果该值是小于0的则是属于异常值。处理缺失值的方法可分为2类:删除记录和数据插补。本节采用数据插补的方式处理缺失值,由于属性列的数据都是数值型数据,所以用常量0替换缺失值。而异常值的处理方法有如下3种。(1)删除含有异常值的记录。(2)视为缺失值。(3)平均值修正:即用前后两个观测值的平均值修正异常值。对于异常值,本节将其视为缺失值,同处理缺失值的方法一样,用常量0替换异常值。数据清洗将缺失值和异常值替换成0可以用MapReduce编程实现,其思路非常简单,针对3.2.2集成的数据,在Mapper类中自定义计数器用于记录数据中缺失值和异常值的记录数,在map函数读取数据,判断读取进来的数据是否含有缺失值,若有则将该值替换成0,同时缺失值计数器加1,再判断数据中是否有异常值,若有异常值则将该值替换成0并且异常值计数器加1。具体的实现代码如代码3-8所示。代码3-8处理缺失值及异常值代码重要部分packagepro_demo;publicclassDataProcessingMapperextendsMapper<LongWritable,Text,Text,NullWritable>{ privateStringsplitter=""; enumDataProcessingCounter{ NullData, AbnormalData } @Override protectedvoidsetup(Mapper<LongWritable,Text,Text,NullWritable>.Contextcontext) throwsIOException,InterruptedException{ splitter=context.getConfiguration().get("SPLITTER"); }.......省略......数据清洗打包成pro_demo.jar并上传至Linux系统/opt目录下。在集群终端的命令行运行如代码3-9所示的命令。代码3-9数据清洗的程序运行命令hadoopjar/opt/pro_demo.jarpro_demo.DataProcessing/movie/gender_genre/part-m-00000/movie/processing_out,运行日志如图3-35所示,其中部分日志省略,从图中可以看出,记录缺失值的计数器NullData的记录结果为0,记录异常值的计数器AbnormalData的记录结果也为0,说明3.2.2集成的数据中没有缺失值和异常值。图3-35处理缺失值和异常值的MapReduce任务运行日志3.2.4划分数据集划分数据集一般来说,聚类算法有3个过程:第一步是通过归纳分析训练样本集来建立聚类器,第二步是用验证数据集来选择最优的模型参数,第三步是用已知类别的测试样本集评估聚类器的准确性,如果准确率是可以接受的,则使用该模型对未知类标号的待测样本集进行预测。因此聚类算法需要把数据分成训练数据,验证数据集和测试数据集。在建立M电影用户聚类器之前,将预处理之后的数据划分成训练数据集,验证数据集和测试数据集。采用8:1:1的比例随机划分数据集,其中训练数据集占80%,验证数据集和测试数据集各占10%。划分数据集可以使用HadoopJavaAPI进行处理,定义读取原数据集并计算原始数据集记录数的方法getSize(FileSystemfs,Pathpath),其中参数path是原数据集预处理之后的存放路径。将原始数据看成一个列表,列表中的元素是每一条数据,定义随机获取80%原始数据的数据下标的方法trainIndex,该方法返回一个Set集合,例如,假如训练数据集得到的数据是原始数据的第1,第3,第5条数据,则trainIndex方法的得到的Set集合的元素就为<1,3,5>。再定义随机获取10%原始数据的数据下标的方法validateIndex,该方法同样返回一个Set集合。trainIndex方法得到的集合与validateIndex得到的集合中的元素是不重复的。划分数据集创建SplitData.java文件,以上所述三个方法的具体实现代码如代码3-10所示。代码3-10读取HDFS的数据并统计记录数的方法/***读取原始数据并统计数据的记录数*@paramfs*@parampath*@return*@throwsException*/publicstaticintgetSize(FileSystemfs,Pathpath)throwsException{ intcount=0; FSDataInputStreamis=fs.open(path); BufferedReaderbr=newBufferedReader(newInputStreamReader(is)); Stringline=""; while((line=br.readLine())!=null){ count++; } }.......省略......

划分数据集设置训练数据集的存储路径为/movie/trainData,验证数据集的存储路径为/movie/validateData,测试数据集的存储路径为/movie/testData,在main函数里面读取原始数据,将读取到的数据分别写入到/movie/trainData,/movie/validateData及/movie/testData。实现代码如代码3-11所示。代码3-10读取HDFS的数据并统计记录数的方法publicstaticvoidmain(String[]args)throwsException{ Configurationconf=newConfiguration(); conf.set("fs.defaultFS","master:8020"); FileSystemfs=FileSystem.get(conf); //获取预处理之后的电影数据路径 Pathmoviedata=newPath("/movie/processing_out/part-m-00000"); //得到电影数据大小 intdatasize=getSize(fs,moviedata); //得到train数据对应原始数据下标 Set<Integer>train_index=trainIndex(datasize); //得到validate数据对应原始数据的下标 Set<Integer>validate_index=validateIndex(datasize,train_index);.......省略......划分数据集在SplitData.java界面上单击右键选择“Run'SplitData.main()'”,运行成功后,在HDFSWebUI查看文件/movie/trainData,/movie/validateData及/movie/testData,得到的训练数据集如图

3-36所示图3-36训练数据集划分数据集测试数据集如图3-37所示,验证测试集如图3-38所示。图3-37测试数据集图3-38验证测试集3.2.5实现用户性别聚类实现用户性别聚类在src/main目录下创建KNN文件夹并激活,在KNN文件夹下创建名为k_demo的Package用于实现用户性别聚类。MapReduce编程实现M电影网站用户聚类的思路如图3-39所示,实现思路描述如下。图3-39MapReduce实现KNN算法思路实现用户性别聚类(1)自定义值类型表示距离和类型,由于KNN算法是计算测试数据与已知类别的训练数据之间的距离,找到距离与测试数据最近的K个训练数据,再根据这些训练所属的类别的众数来判断测试数据的类别。所以在Map阶段需要将测试数据与训练数据的距离及该训练数据的类别作为值输出,程序可以使用Hadoop内置的数据类型Text作为值类型输出距离及类别,但为了提高程序的执行效率,建议自定义值类型表示距离和类别。(2)Map阶段,setup函数读取测试数据。在map函数里读取每条训练数据,遍历测试数据,计算读取进来的训练记录与每条测试数据的距离,计算距离采用的是欧式距离的计算方法,Map输出的键是每条测试数据,输出的值是该测试数据与读取的训练数据的距离和训练数据的类别。(3)Reduce阶段,setup函数初始化参数K值,reduce函数对相同键的值根据距离进行升序排序,取出前K个值,输出reduce函数读取进来的键和这K个值中类别的众数。根据上述的思路分析,可知Map阶段输出的值是距离和类别的组合,为了提高程序的执行效率,笔者不建议使用Hadoop内置的Text类型作为输出的值类型,而是自定义一个值类型表示距离和类别。笔者自定义了一个值类型DistanceAndLabel类,该类实现Writable接口,根据需求定义声明两个对象,一个double类型的distance对象表示距离,一个String类型的label对象表示类别。同时需要注意写入和读取的顺序,这里先把distance写入out输出流,然后再把label写入out输出流。那么在读取的时候就需要先读取distance,接着再读取label。最后还需要重写toString的方法,将距离distance和类别label用逗号连接起来(分隔符可以自己指定),具体实现代码如代码3-12所示。实现用户性别聚类代码3-12自定义值类型重要部分publicclassDistanceAndLabelimplementsWritable{ privatedoubledistance; privateStringlabel; publicDistanceAndLabel(){ } publicDistanceAndLabel(doubledistance,Stringlabel){ this.distance=distance; this.label=label; } publicdoublegetDistance(){ returndistance; } publicvoidsetDistance(doubledistance){ this.distance=distance; }......省略实现用户性别聚类在Map阶段,setup函数读取测试数据,map函数读取训练数据,计算训练数据与测试数据的距离,输出测试数据,以及测试数据与训练数据的距离和训练数据的类别。假设训练数据为(1,1,1,10,48067,0,2,6,18,0,14,0,0,5,5,2,3,20,3,14,3,21,0),测试数据如表3-4所示,计算训练数据与每天测试数据的距离,Map端输出的结果如表3-5所示,从表中可以看出,Map每读取一条训练数据,结果输出的键值对个数是测试数据集的记录数。假设有n条训练数据,m条测试数据,则最终Map输出的键值对个数是n*m。在Mapper类里面,需要定义一个计算距离的方法calDistance(String[]test,String[]train),具体实现代码如代码3-13所示。表3-4测试数据示例5892,0,45,2,10920,4,6,12,2,7,29,7,1,19,14,12,4,5,40,4,27,49,25893,0,25,7,02139,0,9,33,4,3,144,5,0,8,8,4,3,3,8,6,6,47,25894,0,35,0,70748,0,19,8,0,1,24,1,32,75,22,9,2,3,18,1,24,36,05895,0,25,1,43026,3,38,4,0,3,17,6,1,11,0,0,0,0,3,1,22,27,0表3-5Map输出结果key:(5892,0,45,2,10920,4,6,12,2,7,29,7,1,19,14,12,4,5,40,4,27,49,2)value:(2.48,1)key:(5893,0,25,7,02139,0,9,33,4,3,144,5,0,8,8,4,3,3,8,6,6,47,2)value:(3.32,1)key:(5894,0,35,0,70748,0,19,8,0,1,24,1,32,75,22,9,2,3,18,1,24,36,0)value:(1.63,1)key:(5895,0,25,1,43026,3,38,4,0,3,17,6,1,11,0,0,0,0,3,1,22,27,0)value:(7.08,1)实现用户性别聚类代码3-13Mapper类实现publicclassMovieClassifyMapperextendsMapper<LongWritable,Text,Text,DistanceAndLabel>{ privateDistanceAndLabeldistance_label=newDistanceAndLabel(); privateStringsplitter=""; ArrayList<String>testData=newArrayList<String>(); privateStringtestPath=""; @Override protectedvoidsetup(Mapper<LongWritable,Text,Text,DistanceAndLabel>.Contextcontext) throwsIOException,InterruptedException{ Configurationconf=context.getConfiguration(); splitter=conf.get("SPLITTER"); testPath=conf.get("TESTPATH"); //读取测试数据存于列表testData中 FileSystemfs=FileSystem.get(conf); FSDataInputStreamis=fs.open(newPath(testPath)); BufferedReaderbr=newBufferedReader(newInputStreamReader(is)); Stringline="";......省略实现用户性别聚类在Reduce阶段,setup函数初始化K值,reduce函数针对相同的key,对其值根据距离进行升序排序,取出前K个值,并找到这K个值类别的众数,输出key和类别的众数。例如,假设reduce接收到的键值对为<(5892,0,45,2,10920,4,6,12,2,7,29,7,1,19,14,12,4,5,40,4,27,49,2),[(2.48,1),(3.80,1),(6.53,0),(4.21,0)]>,对其值根据距离升序排序,则得到列表[(2.48,1),(3.80,1),(4.21,0),(6.53,0)],设置参数k为3,取出列表的前3个值,得到列表[(2.48,1),(3.80,1),(4.21,0)],这三个值中,类别1出现了2次,类别0出现了1次,即类别众数为1,所以reduce输出<1,(5892,0,45,2,10920,4,6,12,2,7,29,7,1,19,14,12,4,5,40,4,27,49,2)>。Reducer类代码实现需要定义三个方法,第一个方法是根据距离对值进行排序,第二个方法是取出列表的前K个值,第三个方法是找到列表中的类别众数。Reducer类的代码实现如代码3-14所示。代码3-14Reducer类实现publicclassMovieClassifyReducerextendsReducer<Text,DistanceAndLabel,Text,NullWritable>{ privateintk=0; @Override protectedvoidsetup(Reducer<Text,DistanceAndLabel,Text,NullWritable>.Contextcontext) throwsIOException,InterruptedExceptio

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论